[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: johnno1962 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n#\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\nPackage.resolved\n# *.xcodeproj\n#\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n.swiftpm\n\n.build/\n\n# CocoaPods\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# Pods/\n#\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n#\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n"
  },
  {
    "path": "Contents/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>BuildMachineOSBuild</key>\n\t<string>20D74</string>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>InjectionIII</string>\n\t<key>CFBundleIconFile</key>\n\t<string>App.icns</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.johnholdsworth.InjectionIII</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>🔥 HotReloading</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>2.6.0</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>6076</string>\n\t<key>DTCompiler</key>\n\t<string>com.apple.compilers.llvm.clang.1_0</string>\n\t<key>DTPlatformBuild</key>\n\t<string>12D4e</string>\n\t<key>DTPlatformName</key>\n\t<string>macosx</string>\n\t<key>DTPlatformVersion</key>\n\t<string>11.1</string>\n\t<key>DTSDKBuild</key>\n\t<string>20C63</string>\n\t<key>DTSDKName</key>\n\t<string>macosx11.1</string>\n\t<key>DTXcode</key>\n\t<string>1240</string>\n\t<key>DTXcodeBuild</key>\n\t<string>12D4e</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.developer-tools</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.12</string>\n\t<key>LSUIElement</key>\n\t<true/>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2017-20 John Holdsworth. All rights reserved.</string>\n\t<key>NSMainNibFile</key>\n\t<string>MainMenu</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>NSServices</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSMenuItem</key>\n\t\t\t<dict>\n\t\t\t\t<key>default</key>\n\t\t\t\t<string>Injection Goto</string>\n\t\t\t</dict>\n\t\t\t<key>NSMessage</key>\n\t\t\t<string>injectionGoto</string>\n\t\t\t<key>NSPortName</key>\n\t\t\t<string>InjectionIII</string>\n\t\t\t<key>NSSendTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>NSStringPboardType</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>SMPrivilegedExecutables</key>\n\t<dict>\n\t\t<key>com.johnholdsworth.InjectionIII.Helper</key>\n\t\t<string>identifier com.johnholdsworth.InjectionIII.Helper</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "Contents/PkgInfo",
    "content": "APPL????"
  },
  {
    "path": "Contents/Resources/Credits.rtf",
    "content": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1561\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;\\f1\\fnil\\fcharset0 Menlo-Regular;\\f2\\fmodern\\fcharset0 Courier;\n\\f3\\fnil\\fcharset0 HelveticaNeue;}\n{\\colortbl;\\red255\\green255\\blue255;\\red63\\green110\\blue116;\\red255\\green255\\blue255;\\red83\\green98\\blue108;\n\\red0\\green0\\blue0;\\red131\\green108\\blue40;\\red14\\green14\\blue255;}\n{\\*\\expandedcolortbl;;\\csgenericrgb\\c24700\\c43100\\c45600;\\csgenericrgb\\c100000\\c100000\\c100000;\\csgenericrgb\\c32549\\c38431\\c42353;\n\\csgenericrgb\\c0\\c0\\c0;\\csgenericrgb\\c51200\\c42300\\c15700;\\csgenericrgb\\c5500\\c5500\\c100000;}\n\\paperw11900\\paperh16840\\vieww9600\\viewh8400\\viewkind0\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\partightenfactor0\n\n\\f0\\fs24 \\cf0 InjectionIII.app is a mostly Swift rewrite of {\\field{\\*\\fldinst{HYPERLINK \"https://github.com/johnno1962/injectionforxcode\"}}{\\fldrslt \n\\f1\\fs22 injectionforxcode}} that runs in the menu bar. It works slightly differently from previous versions of injection in that it uses a file watcher to detect when a user saves a file in the current project to inject it. You can either use the \"Start Injection\" menu item to bootstrap injection into your application or include the following code in your application:\\\n\\\n\\pard\\tx543\\pardeftab543\\pardirnatural\\partightenfactor0\n\n\\f2 \\cf2 \\cb3 #if DEBUG\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle\")?.load()\\cf0 \\\n\\cf2 //for tvOS:\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle\")?.load()\\cf0 \\\n\\cf2 //Or for macOS:\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle\")?.load()\\cf0 \\\n\\cf2 #endif\\cf0 \\\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardeftab543\\partightenfactor0\n\n\\f0 \\cf0 \\cb1 \\\nOr,  to use with Xcode 10:\\\n\\\n\\pard\\tx543\\pardeftab543\\pardirnatural\\partightenfactor0\n\n\\f2 \\cf2 \\cb3 #if DEBUG\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle\")?.load()\\cf0 \\\n\\cf2 //for tvOS:\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle\")?.load()\\cf0 \\\n\\cf2 //Or for macOS:\\cf0 \\\n\\cf2 Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle\")?.load()\\cf0 \\\n\\cf2 #endif\\cf0 \\\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardeftab543\\partightenfactor0\n\n\\f0 \\cf0 \\cb1 \\\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\partightenfactor0\n\\cf0 For further help go to {\\field{\\*\\fldinst{HYPERLINK \"https://github.com/johnno1962/InjectionIII\"}}{\\fldrslt https://github.com/johnno1962/InjectionIII}} or {\\field{\\*\\fldinst{HYPERLINK \"http://johnholdsworth.com/injection.html\"}}{\\fldrslt http://johnholdsworth.com/injection.html}}\\\n\\\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\partightenfactor0\n\n\\f1\\fs22 \\cf0 Copyright (C) 2016-7 John Holdsworth {\\field{\\*\\fldinst{HYPERLINK \"mailto:injectionIII@johnholdsworth.com\"}}{\\fldrslt injectionIII@johnholdsworth.com}} {\\field{\\*\\fldinst{HYPERLINK \"https://twitter.com/Injection4Xcode\"}}{\\fldrslt \n\\fs24 \\cf4 \\expnd0\\expndtw0\\kerning0\n@Injection4Xcode}}\\\n\\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\fs24 \\cf0 \\expnd0\\expndtw0\\kerning0\nPermission is hereby granted, free of charge, to any person obtaining a copy\\\nof this software and associated documentation files (the \"Software\"), to deal\\\nin the Software without restriction, including without limitation the rights\\\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\\ncopies of the Software, and to permit persons to whom the Software is\\\nfurnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in\\\nall copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \\\nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. \\\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \\\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \\\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\partightenfactor0\n\n\\fs22 \\cf0 \\kerning1\\expnd0\\expndtw0 \\\n\\pard\\tx543\\pardeftab543\\pardirnatural\\partightenfactor0\n\n\\f3\\fs26 \\cf5 \\cb3 This release includes a very slightly modified version of the excellent\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf6 [\\cf5 canviz\\cf6 ](\\cf7 https://code.google.com/p/canviz/\\cf6 )\\cf5  library to render \"dot\" files\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf5 in an HTML canvas which is subject to an MIT license. The changes are to pass\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf5 through the ID of the node to the node label tag (line 212), to reverse\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf5 the rendering of nodes and the lines linking them (line 406) and to\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf5 store edge paths so they can be colored (line 66 and 303) in \"canviz-0.1/canviz.js\".\n\\f0\\fs24 \\cf0 \\\n\\\n\n\\f3\\fs26 \\cf5 It now also includes \\cf6 [\\cf5 CodeMirror\\cf6 ](\\cf7 http://codemirror.net/\\cf6 )\\cf5  JavaScript editor\n\\f0\\fs24 \\cf0 \\\n\n\\f3\\fs26 \\cf5 for the code to be evaluated using injection under an MIT license.\n\\f0\\fs24 \\cf0 \\\n}"
  },
  {
    "path": "Contents/Resources/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 John Holdsworth \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Contents/Resources/README.md",
    "content": "# InjectionIII - overdue Swift rewrite of InjectionForXcode\n\n![Icon](http://johnholdsworth.com/Syringe_128.png)\n\nCode injection allows you to update the implementation of functions and any method of a class, struct or enum incrementally\nin the iOS simulator without having to rebuild or restart your application. This saves the developer a significant amount of time tweaking code or iterating over a design.\nThis start-over implementation of [Injection for Xcode](https://github.com/johnno1962/injectionforxcode)\nhas been built into a standalone app: `InjectionIII.app` which runs in the status bar and is [available from the Mac App Store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12).\n\nThis README includes descriptions of some newer features that are only available in more recent\nreleases of the InjectionIII.app [available on github](https://github.com/johnno1962/InjectionIII/releases).\nYou will need to use one of these releases for Apple Silicon or if you have upgraded to Big Sur\ndue to changes to macOS codesigning that affect the sandboxed App Store version of the app.\n\n![Icon](http://johnholdsworth.com/InjectionUI.gif)\n\n`InjectionIII.app` needs an Xcode 10.2 or greater at the path `/Applications/Xcode.app` , works for `Swift` and `Objective-C` and can be used alongside [AppCode](https://www.jetbrains.com/help/objc/create-a-swiftui-application.html) or by using the [AppCode Plugin](https://github.com/johnno1962/InjectionIII/blob/master/AppCodePlugin/INSTALL.md).\n\nTo understand how InjectionIII works and the techniques it uses consult the book [Swift Secrets](http://books.apple.com/us/book/id1551005489).\n\n### Getting Started\n\nTo use injection, download the app from the App Store and run it. Then, you must add `\"-Xlinker -interposable\"` (without the double quotes) to your project's `\"Other Linker Flags\"` for the Debug target (qualified by the simulator SDK to avoid complications with bitcode). Finally, add one of the following to your application delegate's `applicationDidFinishLaunching:`\n\nXcode 10.2 and later (Swift 5+):\n\n```Swift\n\t#if DEBUG\n\tBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle\")?.load()\n\t//for tvOS:\n\tBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle\")?.load()\n\t//Or for macOS:\n\tBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle\")?.load()\n\t#endif\n```\n\nAdding one of these lines loads a bundle included in the `InjectionIII.app`'s\nresources which connects over a localhost socket to the InjectionII app which runs on the task bar.\nOnce injection is connected, you'll be prompted to select the directory containing the project file for the app you wish to inject. This starts a `file watcher` for that directory inside the Mac app so whenever\nyou save to disk a Swift (or Objective-C) source in the project, the target app is messaged through the socket to compile, link, dynamically load and update the implementation of methods in the file being injected. \n\nIf your project is organised across multiple directories or the project file is not at the root of the source tree you can add other directories to be watched for file changes using the \"Add Directory\"\nmenu item. This list resets when you select a new project.\n\nThe file watcher can be disabled & enabled while the app is running using the status bar men.\nWhile the file watcher is disabled you can still force injections through manually using a hotkey `ctrl-=` (remember to save the file first!)\n\nIf you inject a subclass of `XCTest` InjectionIII will try running that individual test inside your application provided has been compiled at some time in the past and doesn't require test specific support code.\nWhen you run your application without rebuilding (^⌘R), recent injections will be re-applied.\n\nYou can detect when a *class* has been injected in your code (to reload a view controller for example) by adding an `@objc func\ninjected()` class or instance method.  The instance `@objc\nfunc injected()` method relies on a \"sweep\" of all objects in your application to find those of\nthe class you have just injected which can be unreliable when using `unowned` instance variables. If you encounter problems, remomve the injected() method and subscribe to the `\"INJECTION_BUNDLE_NOTIFICATION\"` instead along the lines of the following:\n\n```\n    NotificationCenter.default.addObserver(self,\n        selector: #selector(configureView),\n        name: Notification.Name(\"INJECTION_BUNDLE_NOTIFICATION\"), object: nil)\n```\nIncluded in this release is \"Xprobe\" which allows you to browse and inspect the objects in\nyour application through a web-like interface and execute code against them. Enter text into the search textfield to locate objects quickly by class name.\n\nIf you want to build this project from source (which you may need to do to use injection with macOS apps) you'll need to use:\n\n    git clone https://github.com/johnno1962/InjectionIII --recurse-submodules\n    \n### Available downloads\n\n| Xcode 10.2+ | For Big Sur | AppCode Plugin |\n| ------------- | ------------- | ------------- |\n| [Mac app store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12) | [Github Releases](https://github.com/johnno1962/InjectionIII/releases) | [Install  Injection.jar](https://github.com/johnno1962/InjectionIII/tree/master/AppCodePlugin) |\n\n### Limitations/FAQ\n\nNew releases of InjectionIII use a [different patching technique](http://johnholdsworth.com/dyld_dynamic_interpose.html)\nthan previous versions in that you can now update the implementations of class, struct and enum methods (final or not)\nprovided they have not been inlined which shouldn't be the case for a debug build. You can't however alter the layout of\na class or struct in the course of an injection i.e. add or rearrange properties with storage or add or move methods of a\nnon-final class or your app will likely crash. Also, see the notes below for injecting `SwiftUI` views and how they require\ntype erasure.\n\nIf you have a complex project including Objective-C or C dependancies, using the `-interposable` flag may provoke the following error on linking:\n\n```\nCan't find ordinal for imported symbol for architecture x86_64\n```\nIf this is the case, add the following additional \"Other linker Flags\" and it should go away.\n\n```\n-Xlinker -undefined -Xlinker dynamic_lookup\n```\nIf you inject code which calls a function with default arguments you may\nget an error starting as follows reporting an undefined symbol:\n\n```\n💉 *** dlopen() error: dlopen(/var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib, 2): Symbol not found: _$s13TestInjection15QTNavigationRowC4text10detailText4icon6object13customization6action21accessoryButtonActionACyxGSS_AA08QTDetailG0OAA6QTIconOSgypSgySo15UITableViewCellC_AA5QTRow_AA0T5StyleptcSgyAaT_pcSgAWtcfcfA1_\n Referenced from: /var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib\n Expected in: flat namespace\nin /var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib ***\n```\nIf you encounter this problem, download and build [the unhide project](https://github.com/johnno1962/unhide) then add the following\nas a \"Run Script\", \"Build Phase\" to your project after the linking phase:\n\n```\nUNHIDE=~/bin/unhide.sh\nif [ -f $UNHIDE ]; then\n    $UNHIDE\nelse\n    echo \"File $UNHIDE used for code Injection does not exist. Download and build the https://github.com/johnno1962/unhide project.\"\nfi\n```\nThis changes the visibility of symbols for default argument generators\nand this issue should disappear.\n\nIf you are using Code Coverage, you may need to disable it or you will receive a:\n>\t`Symbol not found: ___llvm_profile_runtime` error.`\n\nGo to `Edit Scheme -> Test -> Options -> Code Coverage` and (temporarily) disable.\n\nKeep in mind global state -- If the file you're injecting has top level variables e.g. singletons, static or global vars\nthey will be reset when you inject the code as the new method implementations will refer to the newly loaded\nobject file containing the type.\n\nAs injection needs to know how to compile Swift files individually it is not compatible with building using\n`Whole Module Optimisation`. A workaround for this is to build with `WMO` switched off so there are\nlogs of individual compiles available then switching `WMO` back on if it suits your workflow better.\n\n### SwiftUI Injection\n\nIt is possible to inject `SwiftUI` interfaces but it requires some minor\ncode changes. This is because when you add elements to an interface or\nuse modifiers that change their type, this changes the return type of the\nbody properties' `Content` which causes a crash. To avoid this you need\nto erase the return type. The easiest way to do this is to add the code below\nto your source somewhere then add the modifier  `.eraseToAnyView()`  at\nthe very end of any declaration of a view's body property that you want to inject:\n\n```Swift\n#if DEBUG\nprivate var loadInjection: () = {\n    #if os(macOS)\n    let bundleName = \"macOSInjection.bundle\"\n    #elseif os(tvOS)\n    let bundleName = \"tvOSInjection.bundle\"\n    #elseif targetEnvironment(simulator)\n    let bundleName = \"iOSInjection.bundle\"\n    #else\n    let bundleName = \"maciOSInjection.bundle\"\n    #endif\n    Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/\"+bundleName)!.load()\n}()\n\nimport Combine\n\npublic let injectionObserver = InjectionObserver()\n\npublic class InjectionObserver: ObservableObject {\n    @Published var injectionNumber = 0\n    var cancellable: AnyCancellable? = nil\n    let publisher = PassthroughSubject<Void, Never>()\n    init() {\n        cancellable = NotificationCenter.default.publisher(for:\n            Notification.Name(\"INJECTION_BUNDLE_NOTIFICATION\"))\n            .sink { [weak self] change in\n            self?.injectionNumber += 1\n            self?.publisher.send()\n        }\n    }\n}\n\nextension View {\n    public func eraseToAnyView() -> some View {\n        _ = loadInjection\n        return AnyView(self)\n    }\n    public func onInjection(bumpState: @escaping () -> ()) -> some View {\n        return self\n            .onReceive(injectionObserver.publisher, perform: bumpState)\n            .eraseToAnyView()\n    }\n}\n#else\nextension View {\n    public func eraseToAnyView() -> some View { return self }\n    public func onInjection(bumpState: @escaping () -> ()) -> some View {\n        return self\n    }\n}\n#endif\n```\n\nTo have the view you are working on redisplay automatically when it is injected it's sufficient\nto add an `@ObservedObject`, initialised to the `injectionObserver` instance as follows:\n\n```Swift\n        .eraseToAnyView()\n    }\n\n    #if DEBUG\n    @ObservedObject var iO = injectionObserver\n    #endif\n```\nYou can make all these changes automatically once you've opened a project using the\n`\"Prepare Project\"` menu item. If you'd like to execute some code each time your interface is injected, use the \n`.onInjection { ... }` modifier instead of .`eraseToAnyView()`.\n\n### macOS Injection\n\nIt is possible to use injection with a macOS/Catalyst project but it is getting progressively more difficult\nwith each release of the OS. You need to make sure to turn off the \"App Sandbox\" and also \"Disable \nLibrary Validation\" under the \"Hardened Runtime\" options for your project while you inject.\n\nWith an Apple Silicon Mac it is possible to run your iOS application natively on macOS.\nYou cuse injection with these apps but as you can't turn off library validation it's a little\ninvolved. You need re-codesign the maciOSInjection.bundle contained in the InjectionIII\napp package using the signing identity used by your target app which you can determine\nfrom the `Sign` phase in your app's build logs. You will also need to set a user default with\nthe path to your project file as the name and the signing identity as the value to injected\ncode changes can be signed properly.\n\nAll this is best done by adding the following as a build phase to your target project:\n\n```\n# Type a script or drag a script file from your workspace to insert its path.\nexport CODESIGN_ALLOCATE\\=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\nINJECTION_APP_RESOURCES=/Applications/InjectionIII.app/Contents/Resources\n/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSInjection.bundle/maciOSInjection\n/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSSwiftUISupport.bundle/maciOSSwiftUISupport\n/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY $INJECTION_APP_RESOURCES/maciOSInjection.bundle/Frameworks/SwiftTrace.framework/SwiftTrace\ndefaults write com.johnholdsworth.InjectionIII \"$PROJECT_FILE_PATH\" $EXPANDED_CODE_SIGN_IDENTITY\n```\n### Storyboard injection\n\nSometimes when you are iterating over a UI it is useful to be able to inject storyboards. This works slightly differently from code injection. To inject changes to a storyboard scene, make your changes then _build_ the project instead of saving the storyboard. The \"nib\" of the currently displayed view controlled should be reloaded and viewDidLoad etc. will be called.\n\n### Vaccine\n\nInjection now includes the higher level `Vaccine` functionality, for more information consult the [project README](https://github.com/zenangst/Vaccine) or one of the [following](https://medium.com/itch-design-no/code-injection-in-swift-c49be095414c) [references](https://medium.com/@robnorback/the-secret-to-1-second-compile-times-in-xcode-9de4ec8345a1).\n\n### Method Tracing menu item (SwiftTrace)\n\nIt's possible to inject tracing aspects into your program that don't\naffect it's operation but log every method call. Where possible\nit will also decorate their arguments. You can add logging to all\nmethods in your app's main bundle or the frameworks it uses\nor trace calls to system frameworks such as UIKit or SwiftUI.\nIf you opt into \"Type Lookup\", custom types in your appliction\ncan also be decorated using the CustomStringConvertable\nconformance or the default formatter for structs.\n\nThese features are implemented by the package [SwiftTrace](https://github.com/johnno1962/SwiftTrace)\nwhich is built into the InjectionBundle. If you want finer grain control of what is being traced,\ninclude the following header file in your project's bridging header and a subset of the internal\napi will be available to Swift (after an injection bundle has been loaded):\n\n```C++\n#import \"/Applications/InjectionIII.app/Contents/Resources/SwiftTrace.h\"\n```\nThe \"Trace Main Bundle\" menu item can be mimicked by using the following call:\n\n```Swift\n NSObject.swiftTraceMainBundleMethods()\n```\nIf you want instead to also trace all Swift calls your application makes to a system\nframework such as SwiftUI you can use the following:\n\n```Swift\n NSObject.swiftTraceMethods(inFrameworkContaining:UIHostingController<ContentView>.self)\n```\nTo include or exclude the methods to be traced use the `methodInclusionPattern`\nand `methodExclusionPattern` class properties of SwiftTrace. For more information\nconsult the [SwiftTrace source repo](https://github.com/johnno1962/SwiftTrace). It's also\npossible to access the Swift API of SwiftTrace directly in your app. For example, to add \na new handler to format a particular type by importing SwiftTrace and adding the\nfollowing to your app's `\"Framework Search Paths\"` and `\"Runpath Search Paths\"`\n(for the Debug configuration):\n\n```\n/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle/Frameworks\n```\nThen, you can use something like the following to register the type:\n\n```\nSwiftTrace.makeTraceable(types: [MovieSwift.MovieRow.Props.self])\n```\nIn this case however the `MovieSwift.MovieRow.Props` type from the excellent \n`MovieSwift` SwiftUI  [example project](https://github.com/Dimillian/MovieSwiftUI)\nis too large to format but can be changed to be a class instead of a struct.\n\nFinally, if you'd like to go directly to the file that defines a logged method, select the\nfully qualified method and use the service `Injection Goto` to open the file declaring\nthat function. (To have the `Injection Goto` item appear on your services context menu\nyou need to select it in System Preferences/Keyboard, tab Shortcuts/Services, under the\n\"Text\" section.)\n\nThere are other SwifTrace features that allow you to \"profile\" your application to optimise\nthe order object files are linked into your application which could potentially minimise\npaging on startup. These are surfaced in the \"Method Tracing\" submenu but if I'm honest,\nthese would only make a difference if you had a very, very large application binary.\n\n### Remote Control\n\nNewer versions of InjectionIII contain a server that allows you to control your development device from your desktop once the service has been started. The UI allows you to record and replay macros of UI actions then verify the device screen against snapshots for end-to-end testing.\n\nTo use, import the Swift Package `https://github.com/johnno1962/Remote.git`\nand call `RemoteCapture.start(\"hostname\")` where hostname is a space\nseparated list of hostnames or IP addreses.\n\nWhen InjectionIII is running, select the \"Remote/Start Server\" menu item to start the\nserver and then run your app. It should connect to the server which will pop up a\nwindow showing the device display and accepting tap events. Events can be\nsaved as `macros` and replayed. If you include a snapshot in a macro this will\nbe compared against the device display (within a tolerance) when you replay\nthe macro for automated testing. Remote can also be used to capture videos\nof your app in operation but, as it operates over the network, it isn't fast enough\nto capture animated transitions.\n\n## SwiftEval - Yes, it's eval() for Swift\n\n![Icon](https://courses.cs.washington.edu/courses/cse190m/10su/lectures/slides/images/drevil.png)\n\nInjectionIII started out as the SwiftEval class which is a [single Swift source](InjectionBundle/SwiftEval.swift)\nthat can be added to your iOS simulator or macOS projects to implement an eval function inside\nclasses that inherit from NSObject. There is a generic form which has the following signature:\n\n```Swift\nextension NSObject {\n\tpublic func eval<T>(_ expression: String, type: T.Type) -> T {\n```\n\nThis takes a Swift expression as a String and returns an entity of the type specified.\nThere is also a shorthand function for expressions of type String which accepts the\ncontents of the String literal as it's argument:\n\n```Swift\n\tpublic func swiftEvalString(contents: String) -> String {\n\t    return eval(\"\\\"\" + expression + \"\\\"\", String.self)\n\t}\n```\n\nAn example of how it is used can be found in the EvalApp example.\n\n```Swift\n    @IBAction func performEval(_: Any) {\n        textView.string = swiftEvalString(contents: textField.stringValue)\n    }\n\n    @IBAction func closureEval(_: Any) {\n        _ = swiftEval(code: closureText.stringValue+\"()\")\n    }\n```\n\nThe code works by adding an extension to your class source containing the expression.\nIt then compiles and loads this new version of the class \"swizzling\" this extension onto\nthe original class. The expression can refer to instance members in the class containing\nthe eval class and global variables & functions  in other class sources.\n\n### Acknowledgements:\n\nThis project includes code from [rentzsch/mach_inject](https://github.com/rentzsch/mach_inject),\n[erwanb/MachInjectSample](https://github.com/erwanb/MachInjectSample),\n[davedelong/DDHotKey](https://github.com/davedelong/DDHotKey) and\n[acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift) under their\nrespective licenses.\n\nThe App Tracing functionality uses the [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) trampoline implementation via the [SwiftTrace](https://github.com/johnno1962/SwiftTrace) project under an MIT license.\n\nSwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook).\nSee the project source and header file included in the app bundle\nfor licensing details.\n\nThis release includes a very slightly modified version of the excellent\n[canviz](https://code.google.com/p/canviz/) library to render \"dot\" files\nin an HTML canvas which is subject to an MIT license. The changes are to pass\nthrough the ID of the node to the node label tag (line 212), to reverse\nthe rendering of nodes and the lines linking them (line 406) and to\nstore edge paths so they can be coloured (line 66 and 303) in \"canviz-0.1/canviz.js\".\n\nIt also includes [CodeMirror](http://codemirror.net/) JavaScript editor\nfor the code to be evaluated using injection under an MIT license.\n\n$Date: 2021/02/18 $\n"
  },
  {
    "path": "Contents/Resources/SwiftTrace.h",
    "content": "//\n//  SwiftTrace.h\n//  SwiftTrace\n//\n//  Created by John Holdsworth on 10/06/2016.\n//  Copyright © 2016 John Holdsworth. All rights reserved.\n//\n//  Repo: https://github.com/johnno1962/SwiftTrace\n//  $Id: //depot/SwiftTrace/SwiftTraceGuts/include/SwiftTrace.h#45 $\n//\n\n#ifndef SWIFTTRACE_H\n#define SWIFTTRACE_H\n\n#import <Foundation/Foundation.h>\n\n//! Project version number for SwiftTrace.\nFOUNDATION_EXPORT double SwiftTraceVersionNumber;\n\n//! Project version string for SwiftTrace.\nFOUNDATION_EXPORT const unsigned char SwiftTraceVersionString[];\n\n// In this header, you should import all the public headers of your framework using statements like #import <SwiftTrace/PublicHeader.h>\n\n/**\n Objective-C inteface to SwftTrace as a category on NSObject\n as a summary of the functionality available. Intended to be\n used from Swift where SwifTrace has been provided from a\n dynamically loaded bundle, for example, from InjectionIII.\n\n Each trace superceeds any previous traces when they where\n not explicit about the class or instance being traced\n (see swiftTraceIntances and swiftTraceInstance). For\n example, the following code:\n\n UIView.swiftTraceBundle()\n UITouch.traceInstances(withSubLevels: 3)\n\n Will put a trace on all of the UIKit frameowrk which is then\n refined by the specific trace for only instances of class\n UITouch to be printed and any calls to UIKit made by those\n methods up to three levels deep.\n */\n@interface NSObject(SwiftTrace)\n/**\n The default regexp used to exclude certain methods from tracing.\n */\n+ (NSString * _Nonnull)swiftTraceDefaultMethodExclusions;\n/**\n Optional filter of methods to be included in subsequent traces.\n */\n@property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodInclusionPattern;\n/**\n Provide a regular expression to exclude methods.\n */\n@property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodExclusionPattern;\n/**\n Real time control over methods to be traced (regular expressions)\n */\n@property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterInclude;\n@property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterExclude;\n/**\n Function type suffixes at end of mangled symbol name.\n */\n@property (nonatomic, class, copy) NSArray<NSString *> * _Nonnull swiftTraceFunctionSuffixes;\n/** Are we tracing? */\n@property (readonly, class) BOOL swiftTracing;\n/** Pointer to common interposed state dictionary */\n@property (readonly, class) void * _Nonnull swiftTraceInterposed;\n/** lookup unknown types */\n@property (class) BOOL swiftTraceTypeLookup;\n/**\n Class will be traced (as opposed to swiftTraceInstances which\n will trace methods declared in super classes as well and only\n for instances of that particular class not any subclasses.)\n*/\n+ (void)swiftTrace;\n/**\n Trace all methods defined in classes contained in the main\n executable of the application.\n */\n+ (void)swiftTraceMainBundle;\n/**\n Trace all methods of classes in the main bundle but also\n up to subLevels of calls made by those methods if a more\n general trace has already been placed on them.\n */\n+ (void)swiftTraceMainBundleWithSubLevels:(int)subLevels;\n/**\n Add a trace to all methods of all classes defined in the\n bundle or framework that contains the receiving class.\n */\n+ (void)swiftTraceBundle;\n/**\n Add a trace to all methods of all classes defined in the\n all frameworks in the app bundle.\n */\n+ (void)swiftTraceFrameworkMethods;\n/**\n Output a trace of methods defined in the bundle containing\n the reciever and up to subLevels of calls made by them.\n */\n+ (void)swiftTraceBundleWithSubLevels:(int)subLevels;\n/**\n Trace classes in the application that have names matching\n the regular expression.\n */\n+ (void)swiftTraceClassesMatchingPattern:(NSString * _Nonnull)pattern;\n/**\n Trace classes in the application that have names matching\n the regular expression and subLevels of cals they make to\n classes that have already been traced.\n */\n+ (void)swiftTraceClassesMatchingPattern:(NSString * _Nonnull)pattern subLevels:(intptr_t)subLevels;\n/**\n Return an array of the demangled names of methods declared\n in the reciving Swift class that can be traced.\n */\n+ (NSArray<NSString *> * _Nonnull)swiftTraceMethodNames;\n/**\nReturn an array of the demangled names of methods declared\nin the Swift class provided.\n*/\n+ (NSArray<NSString *> * _Nonnull)switTraceMethodsNamesOfClass:(Class _Nonnull)aClass;\n/**\n Trace instances of the specific receiving class (including\n the methods of its superclasses.)\n */\n+ (void)swiftTraceInstances;\n/**\n Trace instances of the specific receiving class (including\n the methods of its superclasses and subLevels of previously\n traced methods called by those methods.)\n */\n+ (void)swiftTraceInstancesWithSubLevels:(int)subLevels;\n/**\n Trace a methods (including those of all superclasses) for\n a particular instance only.\n */\n- (void)swiftTraceInstance;\n/**\n Trace methods including those of all superclasses for a\n particular instance only and subLevels of calls they make.\n */\n- (void)swiftTraceInstanceWithSubLevels:(int)subLevels;\n/**\n Trace all protocols contained in the bundle declaring the receiver class\n */\n+ (void)swiftTraceProtocolsInBundle;\n/**\n Trace protocols in bundle with qualifications\n */\n+ (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString * _Nullable)pattern;\n+ (void)swiftTraceProtocolsInBundleWithSubLevels:(int)subLevels;\n+ (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString * _Nullable)pattern subLevels:(int)subLevels;\n/**\n Use interposing to trace all methods in main bundle\n Use swiftTraceInclusionPattern, swiftTraceExclusionPattern to filter\n */\n+ (void)swiftTraceMethodsInFrameworkContaining:(Class _Nonnull)aClass;\n+ (void)swiftTraceMainBundleMethods;\n+ (void)swiftTraceMethodsInBundle:(const char * _Nonnull)bundlePath\n                      packageName:(NSString * _Nullable)packageName;\n+ (void)swiftTraceBundlePath:(const char * _Nonnull)bundlePath;\n/**\n Remove most recent trace\n */\n+ (BOOL)swiftTraceUndoLastTrace;\n/**\n Remove all tracing swizles.\n */\n+ (void)swiftTraceRemoveAllTraces;\n/**\n Remove all interposes from tracing.\n */\n+ (void)swiftTraceRevertAllInterposes;\n/**\n Total elapsed time by traced method.\n */\n+ (NSDictionary<NSString *, NSNumber *> * _Nonnull)swiftTraceElapsedTimes;\n/**\n Invocation counts by traced method.\n */\n+ (NSDictionary<NSString *, NSNumber *> * _Nonnull)swiftTraceInvocationCounts;\n@end\n\n#import <mach-o/loader.h>\n#import <objc/runtime.h>\n#import <dlfcn.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n    IMP _Nonnull imp_implementationForwardingToTracer(void * _Nonnull patch, IMP _Nonnull onEntry, IMP _Nonnull onExit);\n    NSArray<Class> * _Nonnull objc_classArray(void);\n    NSMethodSignature * _Nullable method_getSignature(Method _Nonnull Method);\n    const char * _Nonnull sig_argumentType(id _Nonnull signature, NSUInteger index);\n    const char * _Nonnull sig_returnType(id _Nonnull signature);\n    const char * _Nonnull classesIncludingObjc();\n    void findSwiftSymbols(const char * _Nullable path, const char * _Nonnull suffix, void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, void * _Nonnull typeref, void * _Nonnull typeend));\n    void appBundleImages(void (^ _Nonnull callback)(const char * _Nonnull imageName, const struct mach_header * _Nonnull header, intptr_t slide));\n    const char * _Nullable swiftUIBundlePath();\n    const char * _Nullable callerBundle(void);\n    int fast_dladdr(const void * _Nonnull, Dl_info * _Nonnull);\n#ifdef __cplusplus\n}\n#endif\n\nstruct dyld_interpose_tuple {\n  const void * _Nonnull replacement;\n  const void * _Nonnull replacee;\n};\n\n#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED\n#import <CoreGraphics/CGGeometry.h>\n#define OSRect CGRect\n#define OSMakeRect CGRectMake\n#else\n#define OSRect NSRect\n#define OSMakeRect NSMakeRect\n#endif\n\n@interface ObjcTraceTester: NSObject\n\n- (OSRect)a:(float)a i:(int)i b:(double)b c:(NSString *_Nullable)c o:o s:(SEL _Nullable)s;\n\n@end\n#endif\n\n// Copy paste of fishhook.h follows...\n// ===================================\n\n// Copyright (c) 2013, Facebook, Inc.\n// All rights reserved.\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//   * Redistributions of source code must retain the above copyright notice,\n//     this list of conditions and the following disclaimer.\n//   * Redistributions in binary form must reproduce the above copyright notice,\n//     this list of conditions and the following disclaimer in the documentation\n//     and/or other materials provided with the distribution.\n//   * Neither the name Facebook nor the names of its contributors may be used to\n//     endorse or promote products derived from this software without specific\n//     prior written permission.\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef fishhook_h\n#define fishhook_h\n\n#include <stddef.h>\n#include <stdint.h>\n\n#if !defined(FISHHOOK_EXPORT)\n#define FISHHOOK_VISIBILITY __attribute__((visibility(\"hidden\")))\n#else\n#define FISHHOOK_VISIBILITY __attribute__((visibility(\"default\")))\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif //__cplusplus\n\n/*\n * A structure representing a particular intended rebinding from a symbol\n * name to its replacement\n */\nstruct rebinding {\n  const char * _Nonnull name;\n  void * _Nonnull replacement;\n  void * _Nonnull * _Nullable replaced;\n};\n\n/*\n * For each rebinding in rebindings, rebinds references to external, indirect\n * symbols with the specified name to instead point at replacement for each\n * image in the calling process as well as for all future images that are loaded\n * by the process. If rebind_functions is called more than once, the symbols to\n * rebind are added to the existing list of rebindings, and if a given symbol\n * is rebound more than once, the later rebinding will take precedence.\n */\nFISHHOOK_VISIBILITY\nint rebind_symbols(struct rebinding rebindings[_Nonnull], size_t rebindings_nel);\n\n/*\n * Rebinds as above, but only in the specified image. The header should point\n * to the mach-o header, the slide should be the slide offset. Others as above.\n */\nFISHHOOK_VISIBILITY\nint rebind_symbols_image(void * _Nonnull header,\n                         intptr_t slide,\n                         struct rebinding rebindings[_Nonnull],\n                         size_t rebindings_nel);\n\n#ifdef __cplusplus\n}\n#endif //__cplusplus\n\n#endif //fishhook_h\n\n"
  },
  {
    "path": "Contents/Resources/fishhook.h",
    "content": "// Copyright (c) 2013, Facebook, Inc.\n// All rights reserved.\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//   * Redistributions of source code must retain the above copyright notice,\n//     this list of conditions and the following disclaimer.\n//   * Redistributions in binary form must reproduce the above copyright notice,\n//     this list of conditions and the following disclaimer in the documentation\n//     and/or other materials provided with the distribution.\n//   * Neither the name Facebook nor the names of its contributors may be used to\n//     endorse or promote products derived from this software without specific\n//     prior written permission.\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef fishhook_h\n#define fishhook_h\n\n#include <stddef.h>\n#include <stdint.h>\n\n#if !defined(FISHHOOK_EXPORT)\n#define FISHHOOK_VISIBILITY __attribute__((visibility(\"hidden\")))\n#else\n#define FISHHOOK_VISIBILITY __attribute__((visibility(\"default\")))\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif //__cplusplus\n\n/*\n * A structure representing a particular intended rebinding from a symbol\n * name to its replacement\n */\nstruct rebinding {\n  const char *name;\n  void *replacement;\n  void **replaced;\n};\n\n/*\n * For each rebinding in rebindings, rebinds references to external, indirect\n * symbols with the specified name to instead point at replacement for each\n * image in the calling process as well as for all future images that are loaded\n * by the process. If rebind_functions is called more than once, the symbols to\n * rebind are added to the existing list of rebindings, and if a given symbol\n * is rebound more than once, the later rebinding will take precedence.\n */\nFISHHOOK_VISIBILITY\nint rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);\n\n/*\n * Rebinds as above, but only in the specified image. The header should point\n * to the mach-o header, the slide should be the slide offset. Others as above.\n */\nFISHHOOK_VISIBILITY\nint rebind_symbols_image(void *header,\n                         intptr_t slide,\n                         struct rebinding rebindings[],\n                         size_t rebindings_nel);\n\n#ifdef __cplusplus\n}\n#endif //__cplusplus\n\n#endif //fishhook_h\n\n"
  },
  {
    "path": "Contents/Resources/graph.gv",
    "content": "digraph sweep {\n    node [href=\"javascript:void(click_node('\\N'))\" id=\"\\N\" fontname=\"Arial\"];\n    0 [label=\"UIApplication\" tooltip=\"<UIApplication 0x129704620> #0\" color=\"#000000\"];\n    1 [label=\"ztruct.AppDelegate\" tooltip=\"<ztruct.AppDelegate 0x600003e78310> #1\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    0 -> 1 [label=\"_delegate\" color=\"#000000\" eid=\"1\"];\n    6 [label=\"UIMotionEvent\" tooltip=\"<UIMotionEvent 0x600000060000> #6\" color=\"#000000\"];\n    7 [label=\"BKSAccelerometer\" tooltip=\"<BKSAccelerometer 0x60000186c000> #7\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    6 -> 7 [label=\"_motionAccelerometer\" color=\"#000000\" eid=\"9\"];\n    7 -> 6 [label=\"_delegate\" color=\"#000000\" eid=\"10\"];\n    8 [label=\"NSLock\" tooltip=\"<NSLock 0x60000186c060> #8\" color=\"#000000\"];\n    7 -> 8 [label=\"_lock\" color=\"#000000\" eid=\"11\"];\n    7 -> 6 [label=\"delegate\" color=\"#000000\" eid=\"12\"];\n    14 [label=\"BSSimpleAssertion\" tooltip=\"<BSSimpleAssertion 0x600003218810> #14\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    0 -> 14 [label=\"_keyCommandToken\" color=\"#000000\" eid=\"25\"];\n    15 [label=\"BSAtomicSignal\" tooltip=\"<BSAtomicSignal 0x600003e7ca20> #15\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    14 -> 15 [label=\"_invalidated\" color=\"#000000\" eid=\"26\"];\n    21 [label=\"BSServiceConnectionEndpointMonitor\" tooltip=\"<BSServiceConnectionEndpointMonitor 0x600001f68910> #21\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    0 -> 21 [label=\"_endpointMonitor\" color=\"#000000\" eid=\"33\"];\n    22 [label=\"BSServiceManager\" tooltip=\"<BSServiceManager 0x6000017743f0> #22\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    21 -> 22 [label=\"_manager\" color=\"#000000\" eid=\"34\"];\n    23 [label=\"BSServicesConfiguration\" tooltip=\"<BSServicesConfiguration 0x600003c715c0> #23\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    22 -> 23 [label=\"_configuration\" color=\"#000000\" eid=\"35\"];\n    24 [label=\"RBSService\" tooltip=\"<RBSService 0x600003271050> #24\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    22 -> 24 [label=\"_RBSService\" color=\"#000000\" eid=\"36\"];\n    24 -> 22 [label=\"_delegate\" color=\"#000000\" eid=\"37\"];\n    25 [label=\"RBSConnection\" tooltip=\"<RBSConnection 0x600000e701b0> #25\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    24 -> 25 [label=\"_connection\" color=\"#000000\" eid=\"38\"];\n    26 [label=\"OS_xpc_connection\" tooltip=\"<OS_xpc_connection 0x6000007780d0> #26\" color=\"#000000\"];\n    25 -> 26 [label=\"_connection\" color=\"#000000\" eid=\"39\"];\n    27 [label=\"RBSProcessHandle\" tooltip=\"<RBSProcessHandle 0x60000187f360> #27\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    25 -> 27 [label=\"_handle\" color=\"#000000\" eid=\"40\"];\n    28 [label=\"BSAuditToken\" tooltip=\"<BSAuditToken 0x60000294b600> #28\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    27 -> 28 [label=\"_bsAuditToken\" color=\"#000000\" eid=\"41\"];\n    29 [label=\"RBSEmbeddedAppProcessIdentity\" tooltip=\"<RBSEmbeddedAppProcessIdentity 0x600003226010> #29\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    27 -> 29 [label=\"_identity\" color=\"#000000\" eid=\"42\"];\n    30 [label=\"RBSProcessBundle\" tooltip=\"<RBSProcessBundle 0x60000294b540> #30\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    27 -> 30 [label=\"_bundle\" color=\"#000000\" eid=\"43\"];\n    31 [label=\"RBSProcessInstance\" tooltip=\"<RBSProcessInstance 0x600003c54e20> #31\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    30 -> 31 [label=\"_instance\" color=\"#000000\" eid=\"44\"];\n    31 -> 29 [label=\"_identity\" color=\"#000000\" eid=\"45\"];\n    32 [label=\"RBSProcessIdentifier\" tooltip=\"<RBSProcessIdentifier 0x600003c54da0> #32\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    31 -> 32 [label=\"_identifier\" color=\"#000000\" eid=\"46\"];\n    25 -> 24 [label=\"_serviceDelegate\" color=\"#000000\" eid=\"47\"];\n    33 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001271080> #33\" color=\"#000000\"];\n    25 -> 33 [label=\"_connectionQueue\" color=\"#000000\" eid=\"48\"];\n    34 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001271000> #34\" color=\"#000000\"];\n    25 -> 34 [label=\"_handshakeQueue\" color=\"#000000\" eid=\"49\"];\n    35 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001271300> #35\" color=\"#000000\"];\n    25 -> 35 [label=\"_monitorCalloutQueue\" color=\"#000000\" eid=\"50\"];\n    37 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001271380> #37\" color=\"#000000\"];\n    24 -> 37 [label=\"_calloutQueue\" color=\"#000000\" eid=\"52\"];\n    38 [label=\"BSSimpleAssertion\" tooltip=\"<BSSimpleAssertion 0x600003220c60> #38\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    21 -> 38 [label=\"_registrationLock_assertion\" color=\"#000000\" eid=\"53\"];\n    39 [label=\"BSAtomicSignal\" tooltip=\"<BSAtomicSignal 0x600003e781b0> #39\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    38 -> 39 [label=\"_invalidated\" color=\"#000000\" eid=\"54\"];\n    0 -> 1 [label=\"delegate\" color=\"#000000\" eid=\"55\"];\n    42 [label=\"UISplitViewControllerPanelImpl\" tooltip=\"<UISplitViewControllerPanelImpl 0x1297131d0> #42\" color=\"#000000\"];\n    64 [label=\"ztruct.SceneDelegate\" tooltip=\"<ztruct.SceneDelegate 0x600003c559c0> #64\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    42 -> 64 [label=\"_delegate\" color=\"#000000\" eid=\"100\"];\n    42 -> 64 [label=\"delegate\" color=\"#000000\" eid=\"122\"];\n    100 [label=\"UIButtonLabel\" tooltip=\"<UIButtonLabel 0x1297294d0> #100\" shape=box color=\"#000000\"];\n    102 [label=\"CUIStyleEffectConfiguration\" tooltip=\"<CUIStyleEffectConfiguration 0x600001f45450> #102\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    100 -> 102 [label=\"_cuiStyleEffectConfiguration\" color=\"#000000\" eid=\"173\"];\n    117 [label=\"UILabel\" tooltip=\"<UILabel 0x129614b00> #117\" shape=box color=\"#000000\"];\n    119 [label=\"CUIStyleEffectConfiguration\" tooltip=\"<CUIStyleEffectConfiguration 0x600001f6f7f0> #119\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    117 -> 119 [label=\"_cuiStyleEffectConfiguration\" color=\"#000000\" eid=\"206\"];\n    130 [label=\"UITableView\" tooltip=\"<UITableView 0x12a031e00> #130\" shape=box color=\"#000000\"];\n    131 [label=\"ztruct.MasterViewController\" tooltip=\"<ztruct.MasterViewController 0x12960ed40> #131\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    130 -> 131 [label=\"_dataSource\" color=\"#000000\" eid=\"237\"];\n    132 [label=\"UIAutoRespondingScrollViewControllerKeyboardSupport\" tooltip=\"<UIAutoRespondingScrollViewControllerKeyboardSupport 0x600003c27ea0> #132\" color=\"#000000\"];\n    131 -> 132 [label=\"_keyboardSupport\" color=\"#000000\" eid=\"238\"];\n    132 -> 131 [label=\"_viewController\" color=\"#000000\" eid=\"239\"];\n    131 -> 130 [label=\"_view\" color=\"#000000\" eid=\"240\"];\n    133 [label=\"UINavigationItem\" tooltip=\"<UINavigationItem 0x12960f0f0> #133\" color=\"#000000\"];\n    131 -> 133 [label=\"_navigationItem\" color=\"#000000\" eid=\"241\"];\n    134 [label=\"NSBundle\" tooltip=\"<NSBundle 0x600001f68140> #134\" color=\"#000000\"];\n    131 -> 134 [label=\"_nibBundle\" color=\"#000000\" eid=\"243\"];\n    79 [label=\"UINavigationController\" tooltip=\"<UINavigationController 0x129814600> #79\" color=\"#000000\"];\n    131 -> 79 [label=\"_parentViewController\" color=\"#000000\" eid=\"244\"];\n    135 [label=\"UIStoryboard\" tooltip=\"<UIStoryboard 0x60000187f0c0> #135\" color=\"#000000\"];\n    131 -> 135 [label=\"_storyboard\" color=\"#000000\" eid=\"245\"];\n    136 [label=\"UIBarButtonItem\" tooltip=\"<UIBarButtonItem 0x12971a9b0> #136\" color=\"#000000\"];\n    131 -> 136 [label=\"_editButtonItem\" color=\"#000000\" eid=\"247\"];\n    136 -> 131 [label=\"_target\" color=\"#000000\" eid=\"248\"];\n    136 -> 131 [label=\"_toggleEditing:\" color=\"#000000\" eid=\"250\"];\n    138 [label=\"UITraitCollection\" tooltip=\"<UITraitCollection 0x60000077c8f0> #138\" color=\"#000000\"];\n    131 -> 138 [label=\"_lastNotifiedTraitCollection\" color=\"#000000\" eid=\"251\"];\n    139 [label=\"UINavigationContentAdjustments\" tooltip=\"<UINavigationContentAdjustments 0x600003201c80> #139\" color=\"#000000\"];\n    131 -> 139 [label=\"_navigationInsetAdjustment\" color=\"#000000\" eid=\"252\"];\n    130 -> 131 [label=\"_delegate\" color=\"#000000\" eid=\"262\"];\n    130 -> 131 [label=\"_viewDelegate\" color=\"#000000\" eid=\"303\"];\n    130 -> 131 [label=\"delegate\" color=\"#000000\" eid=\"304\"];\n    41 [label=\"UISplitViewController\" tooltip=\"<UISplitViewController 0x12960e9e0> #41\" color=\"#000000\"];\n    41 -> 64 [label=\"delegate\" color=\"#000000\" eid=\"354\"];\n    189 [label=\"UIWindowScene\" tooltip=\"<UIWindowScene 0x12960d260> #189\" color=\"#000000\"];\n    189 -> 64 [label=\"_delegate\" color=\"#000000\" eid=\"375\"];\n    212 [label=\"FBSWorkspace\" tooltip=\"<FBSWorkspace 0x600001f689b0> #212\" color=\"#000000\"];\n    215 [label=\"BSAtomicSignal\" tooltip=\"<BSAtomicSignal 0x600003e781d0> #215\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    212 -> 215 [label=\"_activateSignal\" color=\"#000000\" eid=\"395\"];\n    216 [label=\"FBSWorkspaceFencingImpl\" tooltip=\"<FBSWorkspaceFencingImpl 0x600002935240> #216\" color=\"#000000\"];\n    218 [label=\"BSMutableIntegerMap\" tooltip=\"<BSMutableIntegerMap 0x600003c51e00> #218\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    216 -> 218 [label=\"_triggerToFenceNameMap\" color=\"#000000\" eid=\"399\"];\n    219 [label=\"BSMutableIntegerSet\" tooltip=\"<BSMutableIntegerSet 0x600003c51ea0> #219\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    216 -> 219 [label=\"_triggersToIgnore\" color=\"#000000\" eid=\"400\"];\n    220 [label=\"BSServiceConnectionEndpointMonitor\" tooltip=\"<BSServiceConnectionEndpointMonitor 0x600001f68a00> #220\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    212 -> 220 [label=\"_connectionEndpointMonitor\" color=\"#000000\" eid=\"402\"];\n    220 -> 22 [label=\"_manager\" color=\"#000000\" eid=\"403\"];\n    220 -> 212 [label=\"_lock_delegate\" color=\"#000000\" eid=\"404\"];\n    221 [label=\"BSSimpleAssertion\" tooltip=\"<BSSimpleAssertion 0x600003214d20> #221\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    220 -> 221 [label=\"_registrationLock_assertion\" color=\"#000000\" eid=\"405\"];\n    222 [label=\"BSAtomicSignal\" tooltip=\"<BSAtomicSignal 0x600003e60020> #222\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    221 -> 222 [label=\"_invalidated\" color=\"#000000\" eid=\"406\"];\n    220 -> 212 [label=\"delegate\" color=\"#000000\" eid=\"407\"];\n    223 [label=\"BSServiceConnectionEndpoint\" tooltip=\"<BSServiceConnectionEndpoint 0x600002920000> #223\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    212 -> 223 [label=\"_defaultShellEndpoint\" color=\"#000000\" eid=\"408\"];\n    224 [label=\"OS_xpc_endpoint\" tooltip=\"<OS_xpc_endpoint 0x600003c44260> #224\" color=\"#000000\"];\n    223 -> 224 [label=\"_endpoint\" color=\"#000000\" eid=\"409\"];\n    211 [label=\"FBSWorkspaceScenesClient\" tooltip=\"<FBSWorkspaceScenesClient 0x60000177c310> #211\" color=\"#000000\"];\n    225 [label=\"BSServiceConnection\" tooltip=\"<BSServiceConnection 0x600001f54500> #225\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    211 -> 225 [label=\"_connection\" color=\"#000000\" eid=\"414\"];\n    226 [label=\"BSXPCServiceConnection\" tooltip=\"<BSXPCServiceConnection 0x600001278d00> #226\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    225 -> 226 [label=\"_connection\" color=\"#000000\" eid=\"415\"];\n    227 [label=\"BSXPCServiceConnectionPeer\" tooltip=\"<BSXPCServiceConnectionPeer 0x6000032140c0> #227\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    226 -> 227 [label=\"_lock_peer\" color=\"#000000\" eid=\"416\"];\n    228 [label=\"BSProcessHandle\" tooltip=\"<BSProcessHandle 0x6000032140f0> #228\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    227 -> 228 [label=\"_processHandle\" color=\"#000000\" eid=\"417\"];\n    229 [label=\"BSAuditToken\" tooltip=\"<BSAuditToken 0x600002935680> #229\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    228 -> 229 [label=\"_auditToken\" color=\"#000000\" eid=\"418\"];\n    230 [label=\"BSMachPortTaskNameRight\" tooltip=\"<BSMachPortTaskNameRight 0x600003221590> #230\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    228 -> 230 [label=\"_taskNameRight\" color=\"#000000\" eid=\"419\"];\n    231 [label=\"BSXPCServiceConnectionMessage\" tooltip=\"<BSXPCServiceConnectionMessage 0x600001864ae0> #231\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    226 -> 231 [label=\"_lock_invalidationMessage\" color=\"#000000\" eid=\"420\"];\n    232 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001271580> #232\" color=\"#000000\"];\n    231 -> 232 [label=\"_targetQueue\" color=\"#000000\" eid=\"421\"];\n    233 [label=\"OS_xpc_dictionary\" tooltip=\"<OS_xpc_dictionary 0x600001864600> #233\" color=\"#000000\"];\n    231 -> 233 [label=\"_message\" color=\"#000000\" eid=\"422\"];\n    234 [label=\"OS_xpc_connection\" tooltip=\"<OS_xpc_connection 0x600000070200> #234\" color=\"#000000\"];\n    231 -> 234 [label=\"_xpcConnection\" color=\"#000000\" eid=\"423\"];\n    235 [label=\"BSXPCServiceConnectionEventHandler\" tooltip=\"<BSXPCServiceConnectionEventHandler 0x600000e78120> #235\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    226 -> 235 [label=\"_lock_eventHandler\" color=\"#000000\" eid=\"424\"];\n    236 [label=\"BSXPCServiceConnectionProxy<FBSWorkspaceServiceClientInterface>\" tooltip=\"<BSXPCServiceConnectionProxy<FBSWorkspaceServiceClientInterface> 0x600002931200> #236\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    235 -> 236 [label=\"_lock_remoteTarget\" color=\"#000000\" eid=\"425\"];\n    237 [label=\"BSObjCProtocol\" tooltip=\"<BSObjCProtocol 0x60000322c870> #237\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    236 -> 237 [label=\"_remoteProtocol\" color=\"#000000\" eid=\"426\"];\n    238 [label=\"Protocol\" tooltip=\"<Protocol 0x1cd7c4678> #238\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    237 -> 238 [label=\"_protocol\" color=\"#000000\" eid=\"427\"];\n    239 [label=\"BSObjCProtocol\" tooltip=\"<BSObjCProtocol 0x60000322c6f0> #239\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    236 -> 239 [label=\"_localProtocol\" color=\"#000000\" eid=\"428\"];\n    240 [label=\"Protocol\" tooltip=\"<Protocol 0x1cd7c65f8> #240\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    239 -> 240 [label=\"_protocol\" color=\"#000000\" eid=\"429\"];\n    236 -> 226 [label=\"_connection\" color=\"#000000\" eid=\"430\"];\n    236 -> 234 [label=\"_XPCConnection\" color=\"#000000\" eid=\"431\"];\n    236 -> 232 [label=\"_XPCConnectionTargetQueue\" color=\"#000000\" eid=\"432\"];\n    235 -> 211 [label=\"_interfaceTarget\" color=\"#000000\" eid=\"433\"];\n    241 [label=\"BSZeroingWeakReference\" tooltip=\"<BSZeroingWeakReference 0x600003c5e020> #241\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    235 -> 241 [label=\"_context\" color=\"#000000\" eid=\"434\"];\n    241 -> 225 [label=\"_object\" color=\"#000000\" eid=\"435\"];\n    242 [label=\"OS_dispatch_queue_serial\" tooltip=\"<OS_dispatch_queue_serial 0x600001278c00> #242\" color=\"#000000\"];\n    235 -> 242 [label=\"_targetQueue\" color=\"#000000\" eid=\"436\"];\n    243 [label=\"BSServiceQuality\" tooltip=\"<BSServiceQuality 0x600003c71fa0> #243\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    235 -> 243 [label=\"_serviceQuality\" color=\"#000000\" eid=\"437\"];\n    244 [label=\"BSServiceInterface\" tooltip=\"<BSServiceInterface 0x60000322dd40> #244\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    235 -> 244 [label=\"_interface\" color=\"#000000\" eid=\"438\"];\n    244 -> 237 [label=\"_server\" color=\"#000000\" eid=\"439\"];\n    244 -> 239 [label=\"_client\" color=\"#000000\" eid=\"440\"];\n    245 [label=\"BSXPCCoder\" tooltip=\"<BSXPCCoder 0x600002931040> #245\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    235 -> 245 [label=\"_initiatingContext\" color=\"#000000\" eid=\"441\"];\n    246 [label=\"OS_xpc_dictionary\" tooltip=\"<OS_xpc_dictionary 0x6000018649c0> #246\" color=\"#000000\"];\n    245 -> 246 [label=\"_message\" color=\"#000000\" eid=\"442\"];\n    247 [label=\"BSXPCServiceConnection\" tooltip=\"<BSXPCServiceConnection 0x600001271400> #247\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    226 -> 247 [label=\"_lock_parent\" color=\"#000000\" eid=\"443\"];\n    247 -> 227 [label=\"_lock_peer\" color=\"#000000\" eid=\"444\"];\n    247 -> 234 [label=\"_lock_connection\" color=\"#000000\" eid=\"445\"];\n    248 [label=\"BSXPCServiceConnectionEventHandler\" tooltip=\"<BSXPCServiceConnectionEventHandler 0x600000e70240> #248\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    247 -> 248 [label=\"_lock_eventHandler\" color=\"#000000\" eid=\"446\"];\n    249 [label=\"BSXPCServiceConnectionProxy\" tooltip=\"<BSXPCServiceConnectionProxy 0x600002971700> #249\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    248 -> 249 [label=\"_lock_remoteTarget\" color=\"#000000\" eid=\"447\"];\n    249 -> 247 [label=\"_connection\" color=\"#000000\" eid=\"448\"];\n    249 -> 234 [label=\"_XPCConnection\" color=\"#000000\" eid=\"449\"];\n    249 -> 232 [label=\"_XPCConnectionTargetQueue\" color=\"#000000\" eid=\"450\"];\n    248 -> 232 [label=\"_targetQueue\" color=\"#000000\" eid=\"451\"];\n    248 -> 243 [label=\"_serviceQuality\" color=\"#000000\" eid=\"452\"];\n    250 [label=\"BSXPCServiceConnectionRootClientEndpointContext\" tooltip=\"<BSXPCServiceConnectionRootClientEndpointContext 0x600002971600> #250\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    247 -> 250 [label=\"_context\" color=\"#000000\" eid=\"453\"];\n    251 [label=\"OS_xpc_endpoint\" tooltip=\"<OS_xpc_endpoint 0x600003c50f80> #251\" color=\"#000000\"];\n    250 -> 251 [label=\"_endpoint\" color=\"#000000\" eid=\"454\"];\n    252 [label=\"BSXPCServiceConnectionChildContext\" tooltip=\"<BSXPCServiceConnectionChildContext 0x60000322cc60> #252\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    226 -> 252 [label=\"_context\" color=\"#000000\" eid=\"455\"];\n    252 -> 250 [label=\"_parent\" color=\"#000000\" eid=\"456\"];\n    211 -> 223 [label=\"_endpoint\" color=\"#000000\" eid=\"457\"];\n    253 [label=\"UIApplicationSceneSettings\" tooltip=\"<UIApplicationSceneSettings 0x600001268380> #253\" color=\"#000000\"];\n    257 [label=\"BSSettings\" tooltip=\"<BSSettings 0x600003c207c0> #257\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    253 -> 257 [label=\"_otherSettings\" color=\"#000000\" eid=\"462\"];\n    258 [label=\"BSMutableIntegerMap\" tooltip=\"<BSMutableIntegerMap 0x600003c20bc0> #258\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    257 -> 258 [label=\"_settingToFlagMap\" color=\"#000000\" eid=\"463\"];\n    259 [label=\"BSMutableIntegerMap\" tooltip=\"<BSMutableIntegerMap 0x600003c20ce0> #259\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    257 -> 259 [label=\"_settingToObjectMap\" color=\"#000000\" eid=\"464\"];\n    260 [label=\"BSCornerRadiusConfiguration\" tooltip=\"<BSCornerRadiusConfiguration 0x60000322f5d0> #260\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    259 -> 260 [label=\"_mapTable\" color=\"#000000\" eid=\"465\"];\n    257 -> 253 [label=\"_descriptionProvider\" color=\"#000000\" eid=\"466\"];\n    261 [label=\"BSSettings\" tooltip=\"<BSSettings 0x600003c20780> #261\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    253 -> 261 [label=\"_transientLocalSettings\" color=\"#000000\" eid=\"467\"];\n    262 [label=\"UIApplicationSceneClientSettings\" tooltip=\"<UIApplicationSceneClientSettings 0x600002937980> #262\" color=\"#000000\"];\n    263 [label=\"BSSettings\" tooltip=\"<BSSettings 0x600003c2cf60> #263\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    262 -> 263 [label=\"_otherSettings\" color=\"#000000\" eid=\"469\"];\n    264 [label=\"BSMutableIntegerMap\" tooltip=\"<BSMutableIntegerMap 0x600003c2cfc0> #264\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    263 -> 264 [label=\"_settingToFlagMap\" color=\"#000000\" eid=\"470\"];\n    265 [label=\"BSMutableIntegerMap\" tooltip=\"<BSMutableIntegerMap 0x600003c2d000> #265\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    263 -> 265 [label=\"_settingToObjectMap\" color=\"#000000\" eid=\"471\"];\n    263 -> 262 [label=\"_descriptionProvider\" color=\"#000000\" eid=\"472\"];\n    266 [label=\"FBSSceneIdentityToken\" tooltip=\"<FBSSceneIdentityToken 0x600003c5fec0> #266\" color=\"#000000\"];\n    266 -> 223 [label=\"_endpoint\" color=\"#000000\" eid=\"475\"];\n    189 -> 64 [label=\"delegate\" color=\"#000000\" eid=\"480\"];\n    40 [label=\"UIWindow\" tooltip=\"<UIWindow 0x1297137b0> #40\" shape=box color=\"#000000\"];\n    269 [label=\"BSSimpleAssertion\" tooltip=\"<BSSimpleAssertion 0x600003227db0> #269\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    40 -> 269 [label=\"_eventFocusDeferralToken\" color=\"#000000\" eid=\"485\"];\n    270 [label=\"BSAtomicSignal\" tooltip=\"<BSAtomicSignal 0x600003e707e0> #270\" style=\"filled\" fillcolor=\"#e0e0e0\" color=\"#000000\"];\n    269 -> 270 [label=\"_invalidated\" color=\"#000000\" eid=\"486\"];\n}\n"
  },
  {
    "path": "Contents/Resources/log.html",
    "content": "<html><header><style>\n\nbody, table { font: 8pt Arial; margin: 0px; border: 3px inset lightgrey; }\nimg.snapshot { border: 1px outset grey; }\ndiv.complete { color: darkgreen; }\ndiv.active { color: orange; }\n\n@media (prefers-color-scheme: dark) {\n    body { background: #292A30; color: #DFDFE0; }\n}\n\n</style><script>\n\nfunction $(id) {\n    return id ? document.getElementById(id) : $('macro');\n}\n\nfunction logSet( html ) {\n    $().innerHTML = \" \"+html;\n}\n\nvar id = 0;\n\nfunction logAdd( entry ) {\n    var div = document.createElement(\"div\");\n    if ( entry.match( /^Ended / ) )\n        entry += \"<p>\";\n    div.innerHTML = \" \"+entry;\n    div.id = ++id;\n    $().appendChild(div);\n    scrollTo(0,1000000);\n}\n\nvar prevID, divIDs;\n\nfunction logAnimate( divID ) {\n    if ( !prevID )\n        divIDs = [];\n    else\n        $(prevID).className = \"complete\";\n    if ( divID == \"\" )\n        divID = null;\n    if ( divID ) {\n        $(divID).className = \"active\";\n        divIDs.push(divID);\n    }\n    else {\n        for ( var i=0 ; i<divIDs.length ; i++ )\n            $(divIDs[i]).className = \"\";\n        ids = null;\n    }\n    prevID = divID;\n}\n\nfunction logUpdate( divID, snapshotHTML ) {\n    $(divID).innerHTML = snapshotHTML;\n}\n\nfunction showSnapshot(img) {\n    prompt(\"snapshot\",img.parentElement.children[2].innerText);\n}\n\n</script></header><body><div id='macro'>\nThis area is fully editable and can be annotated.\n</div>\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 John Holdsworth\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version:5.2\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n//\n//  Repo: https://github.com/johnno1962/HotReloading\n//  $Id: //depot/HotReloading/Package.swift#205 $\n//\n\nimport PackageDescription\nimport Foundation\n\n// This means of locating the IP address of developer's\n// Mac has been replaced by a multicast implementation.\n// If the multicast implementation fails to connect,\n// clone the HotReloading project and hardcode the IP\n// address of your Mac into the hostname value below.\n// Then drag the clone onto your project to have it\n// take precedence over the configured version.\nvar hostname = Host.current().name ?? \"localhost\"\n// hostname = \"192.168.0.243\" // for example\n\nlet simulateDlopenOnDevice = false\n\nlet package = Package(\n    name: \"HotReloading\",\n    platforms: [.macOS(\"10.12\"), .iOS(\"10.0\"), .tvOS(\"10.0\")],\n    products: [\n        .library(name: \"HotReloading\", targets: [\"HotReloading\"]),\n        .library(name: \"HotReloadingGuts\", targets: [\"HotReloadingGuts\"]),\n        .library(name: \"injectiondGuts\", targets: [\"injectiondGuts\"]),\n        .executable(name: \"injectiond\", targets: [\"injectiond\"]),\n    ],\n    dependencies: [\n        .package(url: \"https://github.com/johnno1962/SwiftTrace\",\n                 .upToNextMinor(from: \"8.6.1\")),\n        .package(name: \"SwiftRegex\",\n                 url: \"https://github.com/johnno1962/SwiftRegex5\",\n                 .upToNextMinor(from: \"6.1.2\")),\n        .package(url: \"https://github.com/johnno1962/XprobePlugin\",\n                 .upToNextMinor(from: \"2.9.10\")),\n        .package(name: \"RemotePlugin\",\n                 url: \"https://github.com/johnno1962/Remote\",\n                 .upToNextMinor(from: \"2.3.5\")),\n        .package(url: \"https://github.com/johnno1962/ProfileSwiftUI\",\n                 .upToNextMinor(from: \"1.1.3\")),\n//        .package(url: \"https://github.com/johnno1962/DLKit\",\n//                 .upToNextMinor(from: \"1.2.1\")),\n    ] + (simulateDlopenOnDevice ? [\n        .package(url: \"https://github.com/johnno1962/InjectionScratch\",\n                 .upToNextMinor(from: \"1.2.13\"))] : []),\n    targets: [\n        .target(name: \"HotReloading\", dependencies: [\"HotReloadingGuts\",\n                 .product(name: \"SwiftTraceD\", package: \"SwiftTrace\"),\n                 .product(name: \"Xprobe\", package: \"XprobePlugin\"),\n                 .product(name: \"SwiftRegex\", package: \"SwiftRegex\"),\n                    \"ProfileSwiftUI\" /*, \"DLKit\",\n                    */] + (simulateDlopenOnDevice ? [\"InjectionScratch\"] : [])\n                    /*, linkerSettings: [.unsafeFlags([\n                    \"-Xlinker\", \"-interposable\", \"-undefined\", \"dynamic_lookup\"])]*/),\n        .target(name: \"HotReloadingGuts\",\n                cSettings: [.define(\"DEVELOPER_HOST\", to: \"\\\"\\(hostname)\\\"\")]),\n        .target(name: \"injectiondGuts\"),\n        .target(name: \"injectiond\", dependencies: [\"HotReloadingGuts\", \"injectiondGuts\",\n                                   .product(name: \"SwiftRegex\", package: \"SwiftRegex\"),\n                                   .product(name: \"XprobeUI\", package: \"XprobePlugin\"),\n                                   .product(name: \"RemoteUI\", package: \"RemotePlugin\")],\n                swiftSettings: [.define(\"INJECTION_III_APP\")])],\n    cxxLanguageStandard: .cxx11\n)\n"
  },
  {
    "path": "README.md",
    "content": "# Yes, HotReloading for Swift, Objective-C & C++!\n\nNote: While this was once a way of using the InjectionIII.app on real devices\nand for its development you would not normally need to use this repo any \nmore as you can use the pre-built bundles using the `copy_bundle.sh` \nscript. It has also been largely superseded by the newer and simpler \n[InjectionNext](https://github.com/johnno1962/InjectionNext) project.\nYou should only add the HotReloading product to your main target.\n\nThis project is the [InjectionIII](https://github.com/johnno1962/InjectionIII) app\nfor live code updates available as a Swift Package. i.e.:\n\n![Icon](http://johnholdsworth.com/HotAdding.png)\n\nThen, you can inject function implementations without having to rebuild your app...\n![Icon](http://johnholdsworth.com/HotReloading.png)\n\nTo try out an example project that is already set-up, clone this fork of\n[SwiftUI-Kit](https://github.com/johnno1962/SwiftUI-Kit).\n\nTo use on your project, add this repo as a Swift Package and add \n\"Other Linker Flags\": -Xlinker -interposable. You no longer need\nto add a \"Run Script\" build phase. If want to inject on a device, \nsee the notes below on how to configure the InjectionIII app.\nNote however, on an M1/M2 Mac this project only works with \nan iOS/tvOS 14 or later simulator. Also, due to a quirk of how\nXcode how enables a DEBUG build of Swift Packages, your \n\"configuration\" needs to contain the string \"Debug\".\n\n***Remember not to release your app with this package configured.***\n\nYou should see a message that the app is watching for source file \nchanges in your home directory. You can change this scope by\nadding comma separated list in the environment variable\n`INJECTION_DIRECTORIES`.  Should you want to connect to the \nInjectionIII.app when using the simulator, add the environment \nvariable `INJECTION_DAEMON` to your scheme.\n\nConsult the README of the [InjectionIII](https://github.com/johnno1962/InjectionIII)\nproject for more information in particular how to use it to inject `SwiftUI` using the\n[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) protocol extension.\n\n### HotReloading using VSCode\n\nIt's possible to use HotReloading from inside the VSCode editor and realise a\nform of \"VScode Previews\". Consult [this project](https://github.com/markst/hotreloading-vscode-ios) for the setup required.\n\n### Device Injection\n\nThis version of the HotReloading project and it's dependencies now support\ninjection on a real iOS or tvOS device. \n\nDevice injection now connects to the [InjectionIII.app](https://github.com/johnno1962/InjectionIII)\n([github release](https://github.com/johnno1962/InjectionIII/releases)\n4.6.0 or above) and requires you type the following commands into a Terminal \nthen restart the app to opt into receiving remote connections from a device:\n\n    $ rm ~/Library/Containers/com.johnholdsworth.InjectionIII/Data/Library/Preferences/com.johnholdsworth.InjectionIII.plist\n    $ defaults write com.johnholdsworth.InjectionIII deviceUnlock any\n    \nNote, if you've used the App Store version of InjectionIII in the past,\nthe binary releases have a different preferences file and the two can\nget confused and prevent writing this preference from taking effect.\nThis is why the first `rm` command above can be necessary. If your\ndevice doesn't connect check the app is listening on port `8899`:\n\n```\n% netstat -an | grep LIST | grep 88\ntcp4       0      0  127.0.0.1.8898         *.*                    LISTEN\ntcp4       0      0  *.8899                 *.*                    LISTEN\n```\nIf your device still doesn't connect either add an `INJECTION_HOST`\nenvironment variable to your scheme containg the WiFi IP address of\nthe host you're running the InjectionIII.app on or clone this project and \ncode your mac's IP address into the  `hostname` variable in Package.swift.\nThen, drag the clone onto your project to have it take the place of the\nconfigured Swift Package as outlined in [these instructions](https://developer.apple.com/documentation/xcode/editing-a-package-dependency-as-a-local-package).\n\nNote: as the HotReloading package needs to connect a network\nsocket to your Mac to receive commands and new versions of code, expect\na message the first time you run your app after adding the package\nasking you to \"Trust\" that your app should be allowed to do this.\nLikewise, at the Mac end (as the InjectionIII app needs to open\na network port to accept this connection) you may be prompted for\npermission if you have the macOS firewall turned on.\n\nFor `SwiftUI` you can force screen updates by following the conventions \noutlined in the [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) \nproject then you can experience something like \"Xcode Previews\", except \nfor a fully functional app on an actual device!\n\n### Vapor injection\n\nTo use injection with Vapor web server,  it is now possible to just\ndownload the [InjectionIII.app](https://github.com/johnno1962/InjectionIII)\nand add the following line to be called as the server configures\n(when running Vapor from inside Xcode): \n\n```\n    #if DEBUG && os(macOS)\n    Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle\")?.load()\n    #endif\n```\nIt will also be necessary to add the following argument to your targets:\n\n ```\n     linkerSettings: [.unsafeFlags([\"-Xlinker\", \"-interposable\"],\n             .when(platforms: [.macOS], configuration: .debug))]\n ```\nAs an alternative, you can add this Swift package as a dependency to Vapor's \nPackage.swift of the \"App\" target.\n\n### Thanks to...\n\nThe App Tracing functionality uses the [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) trampoline implementation\nvia the [SwiftTrace](https://github.com/johnno1962/SwiftTrace) project under an MIT license.\n\nSwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook)\nas an alternative to the dyld_dynamic_interpose dynamic loader private api. See the\n project source and header file included in the framework for licensing details.\n\nThe [\"Remote\"](https://github.com/johnno1962/Remote) server in this project which\nallows you to capture videos from your device includes code adapted from\n[acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift)\n\nThis release includes a very slightly modified version of the excellent\n[canviz](https://code.google.com/p/canviz/) library to render \"dot\" files\nin an HTML canvas which is subject to an MIT license. The changes are to pass\nthrough the ID of the node to the node label tag (line 212), to reverse\nthe rendering of nodes and the lines linking them (line 406) and to\nstore edge paths so they can be coloured (line 66 and 303) in \"canviz-0.1/canviz.js\".\n\nIt also includes [CodeMirror](http://codemirror.net/) JavaScript editor for\nthe code to be evaluated in the Xprobe browser under an MIT license.\n\n$Date: 2025/08/03 $\n"
  },
  {
    "path": "Sources/HotReloading/DeviceInjection.swift",
    "content": "//\n//  DeviceInjection.swift\n//\n//  Created by John Holdsworth on 17/03/2022.\n//  Copyright © 2022 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/DeviceInjection.swift#44 $\n//\n//  Code specific to injecting on an actual device.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#if !targetEnvironment(simulator) && SWIFT_PACKAGE && canImport(InjectionScratch)\n#if SWIFT_PACKAGE\nimport SwiftRegex\n#endif\n\nextension SwiftInjection {\n\n    /// Emulate remaining functions of the dynamic linker.\n    /// - Parameter pseudoImage: last image read into memory\n    public class func onDeviceSpecificProcessing(\n        for pseudoImage: MachImage, _ sweepClasses: [AnyClass]) {\n        // register types, protocols, conformances...\n        var section_size: UInt64 = 0\n        for (section, regsiter) in [\n            (\"types\",  \"swift_registerTypeMetadataRecords\"),\n            (\"protos\", \"swift_registerProtocols\"),\n            (\"proto\",  \"swift_registerProtocolConformances\")] {\n            if let section_start =\n                getsectdatafromheader_64(autoBitCast(pseudoImage),\n                     SEG_TEXT, \"__swift5_\"+section, &section_size),\n               section_size != 0, let call: @convention(c)\n                (UnsafeRawPointer, UnsafeRawPointer) -> Void =\n                autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, regsiter)) {\n                call(section_start, section_start+Int(section_size))\n            }\n        }\n        // Redirect symbolic type references to main bundle\n        reverse_symbolics(pseudoImage)\n        // Initialise offsets to ivars\n        adjustIvarOffsets(in: pseudoImage)\n        // Fixup references to Objective-C classes\n        fixupObjcClassReferences(in: pseudoImage)\n        // Fix Objective-C messages to super\n        var supersSize: UInt64 = 0\n        if let injectedClass = sweepClasses.first,\n            let supersSection: UnsafeMutablePointer<AnyClass?> = autoBitCast(\n                getsectdatafromheader_64(autoBitCast(pseudoImage), SEG_DATA,\n                    \"__objc_superrefs\", &supersSize)), supersSize != 0 {\n            supersSection[0] = injectedClass\n        }\n        // Populate \"l_got.*\" descriptor references\n        bindDescriptorReferences(in: pseudoImage)\n    }\n\n    struct ObjcClassMetaData {\n        var metaClass: AnyClass?\n        var metaData: UnsafeMutablePointer<ObjcClassMetaData>? {\n            return autoBitCast(metaClass)\n        }\n        var superClass: AnyClass?\n        var superData: UnsafeMutablePointer<ObjcClassMetaData>? {\n            return autoBitCast(superClass)\n        }\n        var methodCache: UnsafeMutableRawPointer\n        var bits: uintptr_t\n        var data: UnsafeMutablePointer<ObjcReadOnlyMetaData>?\n    }\n\n    public class func fillinObjcClassMetadata(in pseudoImage: MachImage) {\n\n        func getClass(_ sym: UnsafePointer<Int8>)\n            -> UnsafeMutablePointer<ObjcClassMetaData>? {\n            return autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, sym))\n        }\n\n        var sectionSize: UInt64 = 0\n        let info = getsectdatafromheader_64(autoBitCast(pseudoImage),\n                 SEG_DATA, \"__objc_imageinfo\", &sectionSize)\n        let metaNSObject = getClass(\"OBJC_CLASS_$_NSObject\")\n        let emptyCache = dlsym(SwiftMeta.RTLD_DEFAULT, \"_objc_empty_cache\")!\n\n        func fillin(newClass: UnsafeRawPointer, symname: UnsafePointer<Int8>) {\n            let metaData:\n                UnsafeMutablePointer<ObjcClassMetaData> = autoBitCast(newClass)\n            if let oldClass = getClass(symname) {\n                metaData.pointee.methodCache = emptyCache\n                metaData.pointee.superClass = oldClass.pointee.superClass\n                metaData.pointee.metaData?.pointee.methodCache = emptyCache\n                metaData.pointee.metaData?.pointee.metaClass =\n                    metaNSObject?.pointee.metaClass\n                metaData.pointee.metaData?.pointee.superClass =\n                    oldClass.pointee.metaClass // should be super of metaclass..\n                if registerClasses, #available(macOS 10.10, iOS 8.0, tvOS 9.0, *) {\n                    detail(\"\\(newClass): \\(metaData.pointee) -> \" +\n                           \"\\((metaData.pointee.metaData ?? metaData).pointee)\")\n//                    _objc_realizeClassFromSwift(autoBitCast(aClass), oldClass)\n                    objc_readClassPair(autoBitCast(newClass), autoBitCast(info))\n                } else {\n                    // Fallback on earlier versions\n                }\n            }\n        }\n\n        SwiftTrace.forAllClasses(bundlePath: searchLastLoaded()) {\n            (aClass, stop) in\n            var info = Dl_info()\n            let address: UnsafeRawPointer = autoBitCast(aClass)\n            if fast_dladdr(address, &info) != 0, let symname = info.dli_sname {\n                fillin(newClass: address, symname: symname)\n            }\n        }\n    }\n\n    // Used to enumerate methods\n    // on an \"unrealised\" class.\n    struct ObjcMethodMetaData {\n        let name: UnsafePointer<CChar>\n        let type: UnsafePointer<CChar>\n        let impl: IMP\n    }\n\n    struct ObjcMethodListMetaData {\n        let flags: Int32, methodCount: Int32\n        var firstMethod: ObjcMethodMetaData\n    }\n\n    struct ObjcReadOnlyMetaData {\n        let skip: (Int32, Int32, Int32, Int32) = (0, 0, 0, 0)\n        let names: (UnsafeRawPointer?, UnsafePointer<CChar>?)\n        let methods: UnsafeMutablePointer<ObjcMethodListMetaData>?\n    }\n\n    public class func onDevice(swizzle oldClass: AnyClass,\n                               from newClass: AnyClass) -> Int {\n        var swizzled = 0\n        let metaData: UnsafePointer<ObjcClassMetaData> = autoBitCast(newClass)\n        if !class_isMetaClass(oldClass), // class methods...\n           let metaClass = metaData.pointee.metaClass,\n           let metaOldClass = object_getClass(oldClass) {\n            swizzled += onDevice(swizzle: metaOldClass, from: metaClass)\n        }\n\n        let swiftBits: uintptr_t = 0x3\n        guard let roData: UnsafePointer<ObjcReadOnlyMetaData> =\n                autoBitCast(autoBitCast(metaData.pointee.data) & ~swiftBits),\n              let methodInfo = roData.pointee.methods else { return swizzled }\n\n        withUnsafePointer(to: &methodInfo.pointee.firstMethod) {\n            methods in\n            for i in 0 ..< Int(methodInfo.pointee.methodCount) {\n                let selector = sel_registerName(methods[i].name)\n                let method = class_getInstanceMethod(oldClass, selector)\n                let existing = method.flatMap { method_getImplementation($0) }\n                traceAndReplace(existing, replacement: autoBitCast(methods[i].impl),\n                                objcMethod: method, objcClass: newClass) {\n                    (replacement: IMP) -> String? in\n                    if class_replaceMethod(oldClass, selector, replacement,\n                                           methods[i].type) != replacement {\n                        swizzled += 1\n                        return \"Swizzled\"\n                    }\n                    return nil\n                }\n            }\n        }\n\n        return swizzled\n    }\n\n    public class func adjustIvarOffsets(in pseudoImage: MachImage) {\n        var ivarOffsetPtr: UnsafeMutablePointer<ptrdiff_t>!\n\n        // Objective-C source version\n        pseudoImage.symbols(withPrefix: \"_OBJC_IVAR_$_\") {\n            (address, symname, suffix) in\n            if let classname = strdup(suffix),\n               var ivarname = strchr(classname, Int32(UInt8(ascii: \".\"))) {\n               ivarname[0] = 0\n               ivarname += 1\n\n               if let oldClass = objc_getClass(classname) as? AnyClass,\n                  let ivar = class_getInstanceVariable(oldClass, ivarname) {\n                   ivarOffsetPtr = autoBitCast(address)\n                   ivarOffsetPtr.pointee = ivar_getOffset(ivar)\n                   detail(String(cString: classname)+\".\" +\n                          String(cString: ivarname) +\n                          \" offset: \\(ivarOffsetPtr.pointee)\")\n               }\n\n               free(classname)\n            } else {\n                log(\"⚠️ Could not parse ivar: \\(String(cString: symname))\")\n            }\n        }\n\n        // Swift source version\n        findHiddenSwiftSymbols(searchLastLoaded(), \"Wvd\", .any) {\n            (address, symname, _, _) -> Void in\n            if let fieldInfo = SwiftMeta.demangle(symbol: symname),\n               let (classname, ivarname): (String, String) =\n                fieldInfo[#\"direct field offset for (\\S+)\\.\\(?(\\w+) \"#],\n               let oldClass = objc_getClass(classname) as? AnyClass,\n               let ivar = class_getInstanceVariable(oldClass, ivarname),\n               get_protection(autoBitCast(address)) & VM_PROT_WRITE != 0 {\n                ivarOffsetPtr = autoBitCast(address)\n                ivarOffsetPtr.pointee = ivar_getOffset(ivar)\n                detail(classname+\".\"+ivarname +\n                       \" direct offset: \\(ivarOffsetPtr.pointee)\")\n            } else {\n                log(\"⚠️ Could not parse ivar: \\(String(cString: symname))\")\n            }\n        }\n    }\n\n    /// Fixup references to Objective-C classes on device\n    public class func fixupObjcClassReferences(in pseudoImage: MachImage) {\n        var sectionSize: UInt64 = 0\n        if let classNames = objcClassRefs as? [String], classNames.first != \"\",\n           let classRefsSection: UnsafeMutablePointer<AnyClass?> = autoBitCast(\n                getsectdatafromheader_64(autoBitCast(pseudoImage),\n                    SEG_DATA, \"__objc_classrefs\", &sectionSize)) {\n            let nClassRefs = Int(sectionSize)/MemoryLayout<AnyClass>.size\n            let objcClasses = classNames.compactMap {\n                return dlsym(SwiftMeta.RTLD_DEFAULT, \"OBJC_CLASS_$_\"+$0)\n            }\n            if nClassRefs == objcClasses.count {\n                for i in 0 ..< nClassRefs {\n                    classRefsSection[i] = autoBitCast(objcClasses[i])\n                }\n            } else {\n                log(\"⚠️ Number of class refs \\(nClassRefs) does not equal \\(classNames)\")\n            }\n        }\n    }\n\n    /// Populate \"l_got.*\" external references to \"descriptors\"\n    /// - Parameter pseudoImage: lastLoadedImage\n    public class func bindDescriptorReferences(in pseudoImage: MachImage) {\n        if let descriptorSyms = descriptorRefs as? [String],\n            descriptorSyms.first != \"\" {\n            var forces: UnsafeRawPointer?\n            let forcePrefix = \"__swift_FORCE_LOAD_$_\"\n            let forcePrefixLen = strlen(forcePrefix)\n            fast_dlscan(pseudoImage, .any, { symname in\n                return strncmp(symname, forcePrefix, forcePrefixLen) == 0\n            }) { value, symname, _, _ in\n                forces = value\n            }\n\n            if var descriptorRefs:\n                UnsafeMutablePointer<UnsafeMutableRawPointer?> = autoBitCast(forces) {\n                for descriptorSym in descriptorSyms {\n                    descriptorRefs = descriptorRefs.advanced(by: 1)\n                    if let value = dlsym(SwiftMeta.RTLD_DEFAULT, descriptorSym),\n                       descriptorRefs.pointee == nil {\n                        descriptorRefs.pointee = value\n                    } else {\n                        detail(\"⚠️ Could not bind \" + describeImageSymbol(descriptorSym))\n                    }\n                }\n            } else {\n                log(\"⚠️ Could not locate descriptors section\")\n            }\n        }\n    }\n}\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/DynamicCast.swift",
    "content": "//\n//  DynamicCast.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 02/24/2021.\n//  Copyright © 2021 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/DynamicCast.swift#12 $\n//\n//  Dynamic casting in an \"as?\" expression to a type that has been injected.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\npublic func injection_dynamicCast(inp: UnsafeRawPointer,\n    out: UnsafeMutablePointer<UnsafeRawPointer>,\n    from: Any.Type, to: Any.Type, size: size_t) -> Bool {\n    let toName = _typeName(to)\n//    print(\"HERE \\(inp) \\(out) \\(_typeName(from)) \\(toName) \\(size)\")\n    let to = toName.hasPrefix(\"__C.\") ? to :\n        SwiftMeta.lookupType(named: toName, protocols: true) ?? to\n    return DynamicCast.original_dynamicCast?(inp, out,\n        autoBitCast(from), autoBitCast(to), size) ?? false\n}\n\nclass DynamicCast {\n\n    typealias injection_dynamicCast_t = @convention(c)\n    (_ inp: UnsafeRawPointer,\n     _ out: UnsafeMutablePointer<UnsafeRawPointer>,\n     _ from: UnsafeRawPointer, _ to: UnsafeRawPointer,\n     _ size: size_t) -> Bool\n\n    static let swift_dynamicCast = strdup(\"swift_dynamicCast\")!\n    static let original_dynamicCast: injection_dynamicCast_t? =\n        autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, swift_dynamicCast))\n    static var hooked_dynamicCast: UnsafeMutableRawPointer? = {\n        let module = _typeName(DynamicCast.self)\n            .components(separatedBy: \".\")[0]\n        return dlsym(SwiftMeta.RTLD_DEFAULT,\n                     \"$s\\(module.count)\\(module)\" +\n                     \"21injection_dynamicCast\" +\n                     \"3inp3out4from2to4sizeSbSV_\" +\n                     \"SpySVGypXpypXpSitF\")\n    }()\n\n    static var rebinds = original_dynamicCast != nil &&\n                           hooked_dynamicCast != nil ? [\n        rebinding(name: swift_dynamicCast,\n                  replacement: hooked_dynamicCast!,\n                  replaced: nil)] : []\n\n    static var hook_appDynamicCast: Void = {\n        appBundleImages { imageName, header, slide in\n            rebind_symbols_image(autoBitCast(header), slide,\n                                 &rebinds, rebinds.count)\n        }\n    }()\n\n    static func hook_lastInjected() {\n        _ = DynamicCast.hook_appDynamicCast\n        let lastLoaded = _dyld_image_count()-1\n        rebind_symbols_image(\n            UnsafeMutableRawPointer(mutating: lastPseudoImage() ??\n                _dyld_get_image_header(lastLoaded)),\n            lastPseudoImage() != nil ? 0 :\n                _dyld_get_image_vmaddr_slide(lastLoaded),\n            &rebinds, rebinds.count)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/FileWatcher.swift",
    "content": "//\n//  FileWatcher.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 08/03/2015.\n//  Copyright (c) 2015 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/FileWatcher.swift#49 $\n//\n//  Started out as an abstraction to watch files under a directory.\n//  \"Enhanced\" to extract the last modified build log directory by\n//  backdating the event stream to just before the app launched.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#if targetEnvironment(simulator) && !APP_SANDBOXED || os(macOS)\nimport Foundation\n\npublic class FileWatcher: NSObject {\n    public typealias InjectionCallback = (_ filesChanged: NSArray, _ ideProcPath: String) -> Void\n    static var INJECTABLE_PATTERN = try! NSRegularExpression(\n        pattern: \"[^~]\\\\.(mm?|cpp|swift|storyboard|xib)$\")\n\n    static let logsPref = \"HotReloadingBuildLogsDir\"\n    static var derivedLog =\n        UserDefaults.standard.string(forKey: logsPref) {\n        didSet {\n            UserDefaults.standard.set(derivedLog, forKey: logsPref)\n        }\n    }\n\n    var initStream: ((FSEventStreamEventId) -> Void)!\n    var eventsStart =\n        FSEventStreamEventId(kFSEventStreamEventIdSinceNow)\n    #if SWIFT_PACKAGE\n    var eventsToBackdate: UInt64 = 10_000\n    #else\n    var eventsToBackdate: UInt64 = 50_000\n    #endif\n\n    var fileEvents: FSEventStreamRef! = nil\n    var callback: InjectionCallback\n    var context = FSEventStreamContext()\n\n    @objc public init(roots: [String], callback: @escaping InjectionCallback,\n                      runLoop: CFRunLoop? = nil) {\n        self.callback = callback\n        super.init()\n        #if os(macOS)\n        context.info = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)\n        #else\n        guard let FSEventStreamCreate = FSEventStreamCreate else {\n            fatalError(\"Could not locate FSEventStreamCreate\")\n        }\n        #endif\n        initStream = { [weak self] since in\n            guard let self = self else { return }\n            let fileEvents = FSEventStreamCreate(kCFAllocatorDefault,\n             { (streamRef: FSEventStreamRef,\n                clientCallBackInfo: UnsafeMutableRawPointer?,\n                numEvents: Int, eventPaths: UnsafeMutableRawPointer,\n                eventFlags: UnsafePointer<FSEventStreamEventFlags>,\n                eventIds: UnsafePointer<FSEventStreamEventId>) in\n                 #if os(macOS)\n                 let watcher = unsafeBitCast(clientCallBackInfo, to: FileWatcher.self)\n                 #else\n                 guard let watcher = watchers[streamRef] else { return }\n                 #endif\n                 // Check that the event flags include an item renamed flag, this helps avoid\n                 // unnecessary injection, such as triggering injection when switching between\n                 // files in Xcode.\n                 for i in 0 ..< numEvents {\n                     let flag = Int(eventFlags[i])\n                     if (flag & (kFSEventStreamEventFlagItemRenamed | kFSEventStreamEventFlagItemModified)) != 0 {\n                        let changes = unsafeBitCast(eventPaths, to: NSArray.self)\n                         if CFRunLoopGetCurrent() != CFRunLoopGetMain() {\n                             return watcher.filesChanged(changes: changes)\n                         }\n                         DispatchQueue.main.async {\n                             watcher.filesChanged(changes: changes)\n                         }\n                         return\n                     }\n                 }\n             },\n             &self.context, roots as CFArray, since, 0.1,\n             FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes |\n                kFSEventStreamCreateFlagFileEvents))!\n        #if !os(macOS)\n        watchers[fileEvents] = self\n        #endif\n        FSEventStreamScheduleWithRunLoop(fileEvents, runLoop ?? CFRunLoopGetMain(),\n                                         \"kCFRunLoopDefaultMode\" as CFString)\n        _ = FSEventStreamStart(fileEvents)\n        self.fileEvents = fileEvents\n        }\n        initStream(eventsStart)\n    }\n\n    func filesChanged(changes: NSArray) {\n        var changed = Set<String>()\n        #if !INJECTION_III_APP\n        let eventId = FSEventStreamGetLatestEventId(fileEvents)\n        if eventId != kFSEventStreamEventIdSinceNow &&\n            eventsStart == kFSEventStreamEventIdSinceNow {\n            eventsStart = eventId\n            FSEventStreamStop(fileEvents)\n            initStream(max(0, eventsStart-eventsToBackdate))\n            return\n        }\n        #endif\n\n        for path in changes {\n            guard var path = path as? String else { continue }\n            #if !INJECTION_III_APP\n            if path.hasSuffix(\".xcactivitylog\") &&\n                path.contains(\"/Logs/Build/\") {\n                Self.derivedLog = path\n            }\n            if eventId <= eventsStart { continue }\n            #endif\n\n            if Self.INJECTABLE_PATTERN.firstMatch(in: path,\n                range: NSMakeRange(0, path.utf16.count)) != nil &&\n                path.range(of: \"DerivedData/|InjectionProject/|.DocumentRevisions-|@__swiftmacro_|main.mm?$\",\n                            options: .regularExpression) == nil {\n                if let absolute = try? URL(fileURLWithPath: path)\n                    .resourceValues(forKeys: [.canonicalPathKey])\n                    .canonicalPath {\n                    path = absolute\n                }\n                changed.insert(path)\n            }\n        }\n\n        if changed.count != 0 {\n            var path = \"\"\n            #if os(macOS)\n            if let application = NSWorkspace.shared.frontmostApplication {\n                path = getProcPath(pid: application.processIdentifier)\n            }\n            #endif\n            callback(Array(changed) as NSArray, path)\n        }\n    }\n\n    #if os(macOS)\n    deinit {\n        FSEventStreamStop(fileEvents)\n        FSEventStreamInvalidate(fileEvents)\n        FSEventStreamRelease(fileEvents)\n        #if DEBUG\n        NSLog(\"\\(self).deinit()\")\n        #endif\n    }\n\n    func getProcPath(pid: pid_t) -> String {\n        let pathBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))\n        defer {\n            pathBuffer.deallocate()\n        }\n        proc_pidpath(pid, pathBuffer, UInt32(MAXPATHLEN))\n        let path = String(cString: pathBuffer)\n        return path\n    }\n    #endif\n}\n\n#if !os(macOS) // Yes, this api is available inside the simulator...\ntypealias FSEventStreamRef = OpaquePointer\ntypealias ConstFSEventStreamRef = OpaquePointer\nstruct FSEventStreamContext {\n    var version: CFIndex = 0\n    var info: UnsafeRawPointer?\n    var retain: UnsafeRawPointer?\n    var release: UnsafeRawPointer?\n    var copyDescription: UnsafeRawPointer?\n}\ntypealias FSEventStreamCreateFlags = UInt32\ntypealias FSEventStreamEventId = UInt64\ntypealias FSEventStreamEventFlags = UInt32\n\ntypealias FSEventStreamCallback = @convention(c) (ConstFSEventStreamRef, UnsafeMutableRawPointer?, Int, UnsafeMutableRawPointer, UnsafePointer<FSEventStreamEventFlags>, UnsafePointer<FSEventStreamEventId>) -> Void\n\n#if true // avoid linker flags -undefined dynamic_lookup\nlet RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)\nlet FSEventStreamCreate = unsafeBitCast(dlsym(RTLD_DEFAULT, \"FSEventStreamCreate\"), to: (@convention(c) (_ allocator: CFAllocator?, _ callback: FSEventStreamCallback, _ context: UnsafeMutableRawPointer?, _ pathsToWatch: CFArray, _ sinceWhen: FSEventStreamEventId, _ latency: CFTimeInterval, _ flags: FSEventStreamCreateFlags) -> FSEventStreamRef?)?.self)\nlet FSEventStreamScheduleWithRunLoop = unsafeBitCast(dlsym(RTLD_DEFAULT, \"FSEventStreamScheduleWithRunLoop\"), to: (@convention(c) (_ streamRef: FSEventStreamRef, _ runLoop: CFRunLoop, _ runLoopMode: CFString) -> Void).self)\nlet FSEventStreamStart = unsafeBitCast(dlsym(RTLD_DEFAULT, \"FSEventStreamStart\"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> Bool).self)\nlet FSEventStreamGetLatestEventId = unsafeBitCast(dlsym(RTLD_DEFAULT, \"FSEventStreamGetLatestEventId\"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> FSEventStreamEventId).self)\nlet FSEventStreamStop = unsafeBitCast(dlsym(RTLD_DEFAULT, \"FSEventStreamStop\"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> Void).self)\n#else\n@_silgen_name(\"FSEventStreamCreate\")\nfunc FSEventStreamCreate(_ allocator: CFAllocator?, _ callback: FSEventStreamCallback, _ context: UnsafeMutablePointer<FSEventStreamContext>?, _ pathsToWatch: CFArray, _ sinceWhen: FSEventStreamEventId, _ latency: CFTimeInterval, _ flags: FSEventStreamCreateFlags) -> FSEventStreamRef?\n@_silgen_name(\"FSEventStreamScheduleWithRunLoop\")\nfunc FSEventStreamScheduleWithRunLoop(_ streamRef: FSEventStreamRef, _ runLoop: CFRunLoop, _ runLoopMode: CFString)\n@_silgen_name(\"FSEventStreamStart\")\nfunc FSEventStreamStart(_ streamRef: FSEventStreamRef) -> Bool\n#endif\n\nlet kFSEventStreamEventIdSinceNow: UInt64 = 18446744073709551615\nlet kFSEventStreamCreateFlagUseCFTypes: FSEventStreamCreateFlags = 1\nlet kFSEventStreamCreateFlagFileEvents: FSEventStreamCreateFlags = 16\nlet kFSEventStreamEventFlagItemRenamed = 0x00000800\nlet kFSEventStreamEventFlagItemModified = 0x00001000\nfileprivate var watchers = [FSEventStreamRef: FileWatcher]()\n#endif\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/InjectionClient.swift",
    "content": "//\n//  InjectionClient.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 02/24/2021.\n//  Copyright © 2021 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/InjectionClient.swift#91 $\n//\n//  Client app side of HotReloading started by +load\n//  method in HotReloadingGuts/ClientBoot.mm\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n#if SWIFT_PACKAGE\n#if canImport(InjectionScratch)\nimport InjectionScratch\n#endif\nimport Xprobe\nimport ProfileSwiftUI\n\npublic struct HotReloading {\n    public static var stack: Void {\n        injection_stack()\n    }\n}\n#endif\n\n#if os(macOS)\nlet isVapor = true\n#else\nlet isVapor = dlsym(SwiftMeta.RTLD_DEFAULT, VAPOR_SYMBOL) != nil\n#endif\n\n@objc(InjectionClient)\npublic class InjectionClient: SimpleSocket, InjectionReader {\n\n    let injectionQueue = isVapor ? DispatchQueue(label: \"InjectionQueue\") : .main\n    var appVersion: String?\n\n    open func log(_ msg: String) {\n        print(APP_PREFIX+msg)\n    }\n\n    #if canImport(InjectionScratch)\n    func next(scratch: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {\n        return scratch\n    }\n    #endif\n\n    public override func runInBackground() {\n        let builder = SwiftInjectionEval.sharedInstance()\n        builder.tmpDir = NSTemporaryDirectory()\n\n        write(INJECTION_SALT)\n        write(INJECTION_KEY)\n\n        let frameworksPath = Bundle.main.privateFrameworksPath!\n        write(builder.tmpDir)\n        write(builder.arch)\n        let executable = Bundle.main.executablePath!\n        write(executable)\n\n        #if canImport(InjectionScratch)\n        var isiOSAppOnMac = false\n        if #available(iOS 14.0, *) {\n            isiOSAppOnMac = ProcessInfo.processInfo.isiOSAppOnMac\n        }\n        if !isiOSAppOnMac, let scratch = loadScratchImage(nil, 0, self, nil) {\n            log(\"⚠️ You are using device injection which is very much a work in progress. Expect the unexpected.\")\n            writeCommand(InjectionResponse\n                            .scratchPointer.rawValue, with: nil)\n            writePointer(next(scratch: scratch))\n        }\n        #endif\n\n        builder.forceUnhide = { self.writeCommand(InjectionResponse\n                                .forceUnhide.rawValue, with: nil) }\n\n        builder.tmpDir = readString() ?? \"/tmp\"\n\n        builder.createUnhider(executable: executable,\n                              SwiftInjection.objcClassRefs,\n                              SwiftInjection.descriptorRefs)\n\n        if getenv(INJECTION_UNHIDE) != nil {\n            builder.legacyUnhide = true\n            writeCommand(InjectionResponse.legacyUnhide.rawValue, with: \"1\")\n        }\n\n        var frameworkPaths = [String: String]()\n        let isPlugin = builder.tmpDir == \"/tmp\"\n        if (!isPlugin) {\n            var frameworks = [String]()\n            var sysFrameworks = [String]()\n\n            for i in stride(from: _dyld_image_count()-1, through: 0, by: -1) {\n                guard let imageName = _dyld_get_image_name(i),\n                    strstr(imageName, \".framework/\") != nil else {\n                    continue\n                }\n                let imagePath = String(cString: imageName)\n                let frameworkName = URL(fileURLWithPath: imagePath).lastPathComponent\n                frameworkPaths[frameworkName] = imagePath\n                if imagePath.hasPrefix(frameworksPath) {\n                    frameworks.append(frameworkName)\n                } else {\n                    sysFrameworks.append(frameworkName)\n                }\n            }\n\n            writeCommand(InjectionResponse.frameworkList.rawValue, with:\n                frameworks.joined(separator: FRAMEWORK_DELIMITER))\n            write(sysFrameworks.joined(separator: FRAMEWORK_DELIMITER))\n            write(SwiftInjection.packageNames()\n                .joined(separator: FRAMEWORK_DELIMITER))\n        }\n\n        var codesignStatusPipe = [Int32](repeating: 0, count: 2)\n        pipe(&codesignStatusPipe)\n        let reader = SimpleSocket(socket: codesignStatusPipe[0])\n        let writer = SimpleSocket(socket: codesignStatusPipe[1])\n\n        builder.signer = { dylib -> Bool in\n            self.writeCommand(InjectionResponse.getXcodeDev.rawValue,\n                              with: builder.xcodeDev)\n            self.writeCommand(InjectionResponse.sign.rawValue, with: dylib)\n            return reader.readString() == \"1\"\n        }\n\n        SwiftTrace.swizzleFactory = SwiftTrace.LifetimeTracker.self\n\n        if let projectRoot = getenv(INJECTION_PROJECT_ROOT) {\n            writeCommand(InjectionResponse.projectRoot.rawValue,\n                         with: String(cString: projectRoot))\n        }\n        if let derivedData = getenv(INJECTION_DERIVED_DATA) {\n            writeCommand(InjectionResponse.derivedData.rawValue,\n                         with: String(cString: derivedData))\n        }\n\n        // Find client platform\n        #if os(macOS) || targetEnvironment(macCatalyst)\n        var platform = \"Mac\"\n        #elseif os(tvOS)\n        var platform = \"AppleTV\"\n        #elseif os(visionOS)\n        var platform = \"XR\"\n        #elseif os(watchOS)\n        var platform = \"Watch\"\n        #else\n        var platform = \"iPhone\"\n        #endif\n\n        #if targetEnvironment(simulator)\n        platform += \"Simulator\"\n        #else\n        platform += \"OS\"\n        #endif\n        #if os(macOS)\n        platform += \"X\"\n        #endif\n        writeCommand(InjectionResponse.platform.rawValue, with: platform)\n\n        commandLoop:\n        while true {\n            let commandInt = readInt()\n            guard let command = InjectionCommand(rawValue: commandInt) else {\n                log(\"Invalid commandInt: \\(commandInt)\")\n                break\n            }\n            switch command {\n            case .EOF:\n                log(\"EOF received from server..\")\n                break commandLoop\n            case .signed:\n                writer.write(readString() ?? \"0\")\n            case .traceFramework:\n                let frameworkName = readString() ?? \"Misssing framework\"\n                if let frameworkPath = frameworkPaths[frameworkName] {\n                    print(\"\\(APP_PREFIX)Tracing %s\\n\", frameworkPath)\n                    _ = SwiftTrace.interposeMethods(inBundlePath: frameworkPath,\n                                                    packageName: nil)\n                    SwiftTrace.trace(bundlePath:frameworkPath)\n                } else {\n                    log(\"Tracing package \\(frameworkName)\")\n                    let mainBundlePath = Bundle.main.executablePath ?? \"Missing\"\n                    _ = SwiftTrace.interposeMethods(inBundlePath: mainBundlePath,\n                                                    packageName: frameworkName)\n                }\n                filteringChanged()\n            default:\n                process(command: command, builder: builder)\n            }\n        }\n\n        builder.forceUnhide = {}\n        log(\"\\(APP_NAME) disconnected.\")\n    }\n\n    func process(command: InjectionCommand, builder: SwiftEval) {\n        switch command {\n        case .vaccineSettingChanged:\n            if let data = readString()?.data(using: .utf8),\n               let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {\n                builder.vaccineEnabled = json[UserDefaultsVaccineEnabled] as! Bool\n            }\n        case .connected:\n            let projectFile = readString() ?? \"Missing project\"\n            log(\"\\(APP_NAME) connected \\(projectFile)\")\n            builder.projectFile = projectFile\n            builder.derivedLogs = nil;\n        case .watching:\n            log(\"Watching files under the directory \\(readString() ?? \"Missing directory\")\")\n        case .log:\n            log(readString() ?? \"Missing log message\")\n        case .ideProcPath:\n            builder.lastIdeProcPath = readString() ?? \"\"\n        case .invalid:\n            log(\"⚠️ Server has rejected your connection. Are you running InjectionIII.app or start_daemon.sh from the right directory? ⚠️\")\n        case .quietInclude:\n            SwiftTrace.traceFilterInclude = readString()\n        case .include:\n            SwiftTrace.traceFilterInclude = readString()\n            filteringChanged()\n        case .exclude:\n            SwiftTrace.traceFilterExclude = readString()\n            filteringChanged()\n        case .feedback:\n            SwiftInjection.traceInjection = readString() == \"1\"\n        case .lookup:\n            SwiftTrace.typeLookup = readString() == \"1\"\n            if SwiftTrace.swiftTracing {\n                log(\"Discovery of target app's types switched \\(SwiftTrace.typeLookup ? \"on\" : \"off\")\");\n            }\n        case .trace:\n            if SwiftTrace.traceMainBundleMethods() == 0 {\n                log(\"⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's Debug \\\"Other Linker Flags\\\"\")\n            } else {\n                log(\"Added trace to methods in main bundle\")\n            }\n            filteringChanged()\n        case .untrace:\n            SwiftTrace.removeAllTraces()\n        case .traceUI:\n            if SwiftTrace.traceMainBundleMethods() == 0 {\n                log(\"⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's Debug \\\"Other Linker Flags\\\"\")\n            }\n            SwiftTrace.traceMainBundle()\n            log(\"Added trace to methods in main bundle\")\n            filteringChanged()\n        case .traceUIKit:\n            DispatchQueue.main.sync {\n                let OSView: AnyClass = (objc_getClass(\"UIView\") ??\n                    objc_getClass(\"NSView\")) as! AnyClass\n                log(\"Adding trace to the framework containg \\(OSView), this will take a while...\")\n                SwiftTrace.traceBundle(containing: OSView)\n                log(\"Completed adding trace.\")\n            }\n            filteringChanged()\n        case .traceSwiftUI:\n            if let bundleOfAnyTextStorage = swiftUIBundlePath() {\n                log(\"Adding trace to SwiftUI calls.\")\n                _ = SwiftTrace.interposeMethods(inBundlePath: bundleOfAnyTextStorage, packageName:nil)\n                filteringChanged()\n            } else {\n                log(\"Your app doesn't seem to use SwiftUI.\")\n            }\n        case .uninterpose:\n            SwiftTrace.revertInterposes()\n            SwiftTrace.removeAllTraces()\n            log(\"Removed all traces (and injections).\")\n            break;\n        case .stats:\n            let top = 200;\n            print(\"\"\"\n\n                \\(APP_PREFIX)Sorted top \\(top) elapsed time/invocations by method\n                \\(APP_PREFIX)=================================================\n                \"\"\")\n            SwiftInjection.dumpStats(top:top)\n            needsTracing()\n        case .callOrder:\n            print(\"\"\"\n\n                \\(APP_PREFIX)Function names in the order they were first called:\n                \\(APP_PREFIX)===================================================\n                \"\"\")\n            for signature in SwiftInjection.callOrder() {\n                print(signature)\n            }\n            needsTracing()\n        case .fileOrder:\n            print(\"\"\"\n                \\(APP_PREFIX)Source files in the order they were first referenced:\n                \\(APP_PREFIX)=====================================================\n                \\(APP_PREFIX)(Order the source files should be compiled in target)\n                \"\"\")\n            SwiftInjection.fileOrder()\n            needsTracing()\n        case .counts:\n            print(\"\"\"\n                \\(APP_PREFIX)Counts of live objects by class:\n                \\(APP_PREFIX)================================\n                \"\"\")\n            SwiftInjection.objectCounts()\n            needsTracing()\n        case .fileReorder:\n            writeCommand(InjectionResponse.callOrderList.rawValue,\n                         with:SwiftInjection.callOrder().joined(separator: CALLORDER_DELIMITER))\n            needsTracing()\n        case .copy:\n            if let data = readData() {\n                injectionQueue.async {\n                    var err: String?\n                    do {\n                        builder.injectionNumber += 1\n                        try data.write(to: URL(fileURLWithPath: \"\\(builder.tmpfile).dylib\"))\n                        try SwiftInjection.inject(tmpfile: builder.tmpfile)\n                    } catch {\n                        self.log(\"⚠️ Injection error: \\(error)\")\n                        err = \"\\(error)\"\n                    }\n                    let response: InjectionResponse = err != nil ? .error : .complete\n                    self.writeCommand(response.rawValue, with: err)\n                }\n            }\n        case .pseudoUnlock:\n            #if canImport(InjectionScratch)\n            presentInjectionScratch(readString() ?? \"\")\n            #endif\n        case .objcClassRefs:\n            if let array = readString()?\n                .components(separatedBy: \",\") as NSArray?,\n               let mutable = array.mutableCopy() as? NSMutableArray {\n                SwiftInjection.objcClassRefs = mutable\n            }\n        case .descriptorRefs:\n            if let array = readString()?\n                .components(separatedBy: \",\") as NSArray?,\n               let mutable = array.mutableCopy() as? NSMutableArray {\n                SwiftInjection.descriptorRefs = mutable\n            }\n        case .setXcodeDev:\n            if let xcodeDev = readString() {\n                builder.xcodeDev = xcodeDev\n            }\n        case .appVersion:\n            appVersion = readString()\n            writeCommand(InjectionResponse.buildCache.rawValue,\n                         with: builder.buildCacheFile)\n        case .profileUI:\n            DispatchQueue.main.async {\n                ProfileSwiftUI.profile()\n            }\n        default:\n            processOnMainThread(command: command, builder: builder)\n        }\n    }\n\n    func processOnMainThread(command: InjectionCommand, builder: SwiftEval) {\n        guard let changed = self.readString() else {\n            log(\"⚠️ Could not read changed filename?\")\n            return\n        }\n        #if canImport(InjectionScratch)\n        if command == .pseudoInject,\n           let imagePointer = self.readPointer() {\n            var percent = 0.0\n            pushPseudoImage(changed, imagePointer)\n            guard let imageEnd = loadScratchImage(imagePointer,\n                self.readInt(), self, &percent) else { return }\n\n            DispatchQueue.main.async {\n                do {\n                    builder.injectionNumber += 1\n                    let tmpfile = String(cString: searchLastLoaded())\n                    let newClasses = try SwiftEval.instance.extractClasses(dl: UnsafeMutableRawPointer(bitPattern: ~0)!, tmpfile: tmpfile)\n                    try SwiftInjection.inject(tmpfile: tmpfile, newClasses: newClasses)\n                } catch {\n                    NSLog(\"Pseudo: \\(error)\")\n                }\n                if percent > 75 {\n                    print(String(format: \"\\(APP_PREFIX)You have used %.1f%% of InjectionScratch space.\", percent))\n                }\n                self.writeCommand(InjectionResponse.scratchPointer.rawValue, with: nil)\n                self.writePointer(self.next(scratch: imageEnd))\n            }\n            return\n        }\n        #endif\n        injectionQueue.async {\n            var err: String?\n            switch command {\n            case .load:\n                do {\n                    builder.injectionNumber += 1\n                    try SwiftInjection.inject(tmpfile: changed)\n\n                    let countKey = \"__injectionsPerformed\", howOften = 100\n                    let count = UserDefaults.standard.integer(forKey: countKey)+1\n                    UserDefaults.standard.set(count, forKey: countKey)\n                    if count % howOften == 0 && getenv(\"INJECTION_SPONSOR\") == nil {\n                        SwiftInjection.log(\"\"\"\n                            ℹ️ Seems like you're using injection quite a bit. \\\n                            Have you considered sponsoring the project at \\\n                            https://github.com/johnno1962/\\(APP_NAME) or \\\n                            asking your boss if they should? (This message \\\n                            prints every \\(howOften) injections.)\n                            \"\"\")\n                    }\n                } catch {\n                    err = error.localizedDescription\n                }\n            case .inject:\n                if changed.hasSuffix(\"storyboard\") || changed.hasSuffix(\"xib\") {\n                    #if os(iOS) || os(tvOS)\n                    if !NSObject.injectUI(changed) {\n                        err = \"Interface injection failed\"\n                    }\n                    #else\n                    err = \"Interface injection not available on macOS.\"\n                    #endif\n                } else {\n                    builder.forceUnhide = { builder.startUnhide() }\n                    SwiftInjection.inject(classNameOrFile: changed)\n                }\n            #if SWIFT_PACKAGE\n            case .xprobe:\n                Xprobe.connect(to: nil, retainObjects:true)\n                Xprobe.search(\"\")\n            case .eval:\n                let parts = changed.components(separatedBy:\"^\")\n                guard let pathID = Int(parts[0]) else { break }\n                self.writeCommand(InjectionResponse.pause.rawValue, with:\"5\")\n                if let object = (xprobePaths[pathID] as? XprobePath)?\n                    .object() as? NSObject, object.responds(to: Selector((\"swiftEvalWithCode:\"))),\n                   let code = (parts[3] as NSString).removingPercentEncoding,\n                   object.swiftEval(code: code) {\n                } else {\n                    self.log(\"Xprobe: Eval only works on NSObject subclasses where the source file has the same name as the class and is in your project.\")\n                }\n                Xprobe.write(\"$('BUSY\\(pathID)').hidden = true; \")\n            #endif\n            default:\n                self.log(\"⚠️ Unimplemented command: #\\(command.rawValue). \" +\n                         \"Are you running the most recent versions?\")\n            }\n            let response: InjectionResponse = err != nil ? .error : .complete\n            self.writeCommand(response.rawValue, with: err)\n        }\n    }\n\n    func needsTracing() {\n        if !SwiftTrace.swiftTracing {\n            log(\"⚠️ You need to have traced something to gather stats.\")\n        }\n    }\n\n    func filteringChanged() {\n        if SwiftTrace.swiftTracing {\n            let exclude = SwiftTrace.traceFilterExclude\n            if let include = SwiftTrace.traceFilterInclude {\n                print(String(format: exclude != nil ?\n                   \"\\(APP_PREFIX)Filtering trace to include methods matching '%@' but not '%@'.\" :\n                   \"\\(APP_PREFIX)Filtering trace to include methods matching '%@'.\",\n                   include, exclude != nil ? exclude! : \"\"))\n            } else {\n                print(String(format: exclude != nil ?\n                   \"\\(APP_PREFIX)Filtering trace to exclude methods matching '%@'.\" :\n                   \"\\(APP_PREFIX)Not filtering trace (Menu Item: 'Set Filters')\",\n                   exclude != nil ? exclude! : \"\"))\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/InjectionStats.swift",
    "content": "//\n//  InjectionStats.swift\n//  \n//  Created by John Holdsworth on 26/10/2022.\n//  Copyright © 2022 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/InjectionStats.swift#4 $\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\nextension SwiftInjection {\n\n    @objc public class func dumpStats(top: Int) {\n        let invocationCounts =  SwiftTrace.invocationCounts()\n        for (method, elapsed) in SwiftTrace.sortedElapsedTimes(onlyFirst: top) {\n            print(\"\\(String(format: \"%.1f\", elapsed*1000.0))ms/\\(invocationCounts[method] ?? 0)\\t\\(method)\")\n        }\n    }\n\n    @objc public class func callOrder() -> [String] {\n        return SwiftTrace.callOrder().map { $0.signature }\n    }\n\n    @objc public class func fileOrder() {\n        let builder = SwiftEval.sharedInstance()\n        let signatures = callOrder()\n\n        guard let projectRoot = builder.projectFile.flatMap({\n                URL(fileURLWithPath: $0).deletingLastPathComponent().path+\"/\"\n            }),\n            let (_, logsDir) =\n                try? builder.determineEnvironment(classNameOrFile: \"\") else {\n            log(\"File ordering not available.\")\n            return\n        }\n\n        let tmpfile = builder.tmpDir+\"/eval101\"\n        var found = false\n\n        SwiftEval.uniqueTypeNames(signatures: signatures) { typeName in\n            if !typeName.contains(\"(\"), let (_, foundSourceFile) =\n                try? builder.findCompileCommand(logsDir: logsDir,\n                    classNameOrFile: typeName, tmpfile: tmpfile) {\n                print(foundSourceFile\n                        .replacingOccurrences(of: projectRoot, with: \"\"))\n                found = true\n            }\n        }\n\n        if !found {\n            log(\"Do you have the right project selected?\")\n        }\n    }\n\n    @objc public class func packageNames() -> [String] {\n        var packages = Set<String>()\n        for suffix in SwiftTrace.traceableFunctionSuffixes {\n            findSwiftSymbols(Bundle.main.executablePath!, suffix) {\n                (_, symname: UnsafePointer<Int8>, _, _) in\n                if let sym = SwiftMeta.demangle(symbol: String(cString: symname)),\n                    !sym.hasPrefix(\"(extension in \"),\n                    let endPackage = sym.firstIndex(of: \".\") {\n                    packages.insert(sym[..<(endPackage+0)])\n                }\n            }\n        }\n        return Array(packages)\n    }\n\n    @objc public class func objectCounts() {\n        for (className, count) in SwiftTrace.liveObjects\n            .map({(_typeName(autoBitCast($0.key)), $0.value.count)})\n            .sorted(by: {$0.0 < $1.0}) {\n            print(\"\\(count)\\t\\(className)\")\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/ObjcInjection.swift",
    "content": "//\n//  ObjcInjection.swift\n//\n//  Created by John Holdsworth on 17/03/2022.\n//  Copyright © 2022 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/ObjcInjection.swift#23 $\n//\n//  Code specific to \"classic\" Objective-C method swizzling.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\nextension SwiftInjection.MachImage {\n    func symbols(withPrefix: UnsafePointer<CChar>,\n                 apply: @escaping (UnsafeRawPointer, UnsafePointer<CChar>,\n                                   UnsafePointer<CChar>) -> Void) {\n        let prefixLen = strlen(withPrefix)\n        fast_dlscan(self, .any, {\n            return strncmp($0, withPrefix, prefixLen) == 0}) {\n            (address, symname, _, _) in\n            apply(address, symname, symname + prefixLen - 1)\n        }\n    }\n}\n\nextension SwiftInjection {\n\n    public typealias MachImage = UnsafePointer<mach_header>\n\n    /// New method of swizzling based on symbol names\n    /// - Parameters:\n    ///   - oldClass: original class to be swizzled\n    ///   - tmpfile: no longer used\n    /// - Returns: # methods swizzled\n    public class func injection(swizzle oldClass: AnyClass, tmpfile: String) -> Int {\n        var methodCount: UInt32 = 0, swizzled = 0\n        if let methods = class_copyMethodList(oldClass, &methodCount) {\n            for i in 0 ..< Int(methodCount) {\n                swizzled += swizzle(oldClass: oldClass,\n                    selector: method_getName(methods[i]), tmpfile)\n            }\n            free(methods)\n        }\n        return swizzled\n    }\n\n    /// Swizzle the newly loaded implementation of a selector onto oldClass\n    /// - Parameters:\n    ///   - oldClass: orignal class to be swizzled\n    ///   - selector: method selector to be swizzled\n    ///   - tmpfile: no longer used\n    /// - Returns: # methods swizzled\n    public class func swizzle(oldClass: AnyClass, selector: Selector,\n                              _ tmpfile: String) -> Int {\n        var swizzled = 0\n        if let method = class_getInstanceMethod(oldClass, selector),\n            let existing = unsafeBitCast(method_getImplementation(method),\n                                         to: UnsafeMutableRawPointer?.self),\n            let selsym = originalSym(for: existing) {\n            if let replacement = fast_dlsym(lastLoadedImage(), selsym) {\n                traceAndReplace(existing, replacement: replacement,\n                                objcMethod: method, objcClass: oldClass) {\n                    (replacement: IMP) -> String? in\n                    if class_replaceMethod(oldClass, selector, replacement,\n                                           method_getTypeEncoding(method)) != nil {\n                        swizzled += 1\n                        return \"Swizzled\"\n                    }\n                    return nil\n                }\n            } else {\n                detail(\"⚠️ Swizzle failed \"+describeImageSymbol(selsym))\n            }\n        }\n        return swizzled\n    }\n\n    /// Fallback to make sure at least the @objc func injected() and viewDidLoad() methods are swizzled\n    public class func swizzleBasics(oldClass: AnyClass, tmpfile: String) -> Int {\n        var swizzled = swizzle(oldClass: oldClass, selector: injectedSEL, tmpfile)\n        #if os(iOS) || os(tvOS)\n        swizzled += swizzle(oldClass: oldClass, selector: viewDidLoadSEL, tmpfile)\n        #endif\n        return swizzled\n    }\n\n    /// Original Objective-C swizzling\n    /// - Parameters:\n    ///   - oldClass: Original class to be swizzle\n    ///   - newClass: Newly loaded class\n    /// - Returns: # of methods swizzled\n    public class func injection(swizzle oldClass: AnyClass?,\n                                from newClass: AnyClass?) -> Int {\n        var methodCount: UInt32 = 0, swizzled = 0\n        if let methods = class_copyMethodList(newClass, &methodCount) {\n            for i in 0 ..< Int(methodCount) {\n                let selector = method_getName(methods[i])\n                let replacement = method_getImplementation(methods[i])\n                guard let method = class_getInstanceMethod(oldClass, selector) ??\n                                    class_getInstanceMethod(newClass, selector),\n                      let existing = i < 0 ? nil : method_getImplementation(method) else {\n                    continue\n                }\n                traceAndReplace(existing, replacement: autoBitCast(replacement),\n                                objcMethod: methods[i], objcClass: newClass) {\n                    (replacement: IMP) -> String? in\n                    if class_replaceMethod(oldClass, selector, replacement,\n                        method_getTypeEncoding(methods[i])) != replacement {\n                        swizzled += 1\n                        return \"Swizzled\"\n                    }\n                    return nil\n                }\n            }\n            free(methods)\n        }\n        return swizzled\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/ReducerInjection.swift",
    "content": "//\n//  ReducerInjection.swift\n//\n//  Created by John Holdsworth on 09/06/2022.\n//  Copyright © 2022 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/ReducerInjection.swift#12 $\n//\n//  Support for injecting \"The Composble Architecture\" Reducers using TCA fork:\n//  https://github.com/thebrowsercompany/swift-composable-architecture/tree/develop\n//  Top level Reducer var initialisations are wrapped in ARCInjectable() call.\n//  Reducers are now deprecated in favour of using the new \"ReducerProtocol\".\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\nextension NSObject {\n\n    @objc\n    public func registerInjectableTCAReducer(_ symbol: String) {\n        SwiftInjection.injectableReducerSymbols.insert(symbol)\n    }\n}\n\nextension SwiftInjection {\n\n    static var injectableReducerSymbols = Set<String>()\n    \n    static var checkReducerInitializers: Void = {\n            var expectedInjectableReducerSymbols = Set<String>()\n\n            findHiddenSwiftSymbols(searchBundleImages(), \"Reducer_WZ\", .any) {\n                _, symname, _, _ in\n                expectedInjectableReducerSymbols.insert(String(cString: symname))\n            }\n\n            for symname in expectedInjectableReducerSymbols\n                .subtracting(injectableReducerSymbols) {\n                let sym = SwiftMeta.demangle(symbol: symname) ?? symname\n                let variable = sym.components(separatedBy: \" \").last ?? sym\n                log(\"⚠️ \\(variable) is not injectable (or unused), wrap it with ARCInjectable\")\n            }\n    }()\n\n    /// Support for re-initialising \"The Composable Architecture\", \"Reducer\"\n    /// variables declared at the top level. Requires custom version of TCA:\n    /// https://github.com/thebrowsercompany/swift-composable-architecture/tree/develop\n    public class func reinitializeInjectedReducers(_ tmpfile: String,\n        reinitialized: UnsafeMutablePointer<[SymbolName]>) {\n        _ = checkReducerInitializers\n        findHiddenSwiftSymbols(searchLastLoaded(), \"_WZ\", .local) {\n            accessor, symname, _, _ in\n            if injectableReducerSymbols.contains(String(cString: symname)) {\n                typealias OneTimeInitialiser = @convention(c) () -> Void\n                let reinitialise: OneTimeInitialiser = autoBitCast(accessor)\n                reinitialise()\n                reinitialized.pointee.append(symname)\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/StandaloneInjection.swift",
    "content": "//\n//  StandaloneInjection.swift\n//\n//  Created by John Holdsworth on 15/03/2022.\n//  Copyright © 2022 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/StandaloneInjection.swift#77 $\n//\n//  Standalone version of the HotReloading version of the InjectionIII project\n//  https://github.com/johnno1962/InjectionIII. This file allows you to\n//  add HotReloading to a project without having to add a \"Run Script\"\n//  build phase to run the daemon process.\n//\n//  The most recent change was for the InjectionIII.app injection bundles\n//  to fall back to this implementation if the user is not running the app.\n//  This was made possible by using the FileWatcher to find the build log\n//  directory in DerivedData of the most recently built project.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#if targetEnvironment(simulator) && !APP_SANDBOXED || os(macOS)\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n@objc(StandaloneInjection)\nclass StandaloneInjection: InjectionClient {\n\n    static var singleton: StandaloneInjection?\n    var watchers = [FileWatcher]()\n\n    override func runInBackground() {\n        let builder = SwiftInjectionEval.sharedInstance()\n        builder.tmpDir = NSTemporaryDirectory()\n        #if SWIFT_PACKAGE\n        let swiftTracePath = String(cString: swiftTrace_path())\n        // convert SwiftTrace path into path to logs.\n        builder.derivedLogs = swiftTracePath.replacingOccurrences(of:\n            #\"SourcePackages/checkouts/SwiftTrace/SwiftTraceGutsD?/SwiftTrace.mm$\"#,\n            with: \"Logs/Build\", options: .regularExpression)\n        if builder.derivedLogs == swiftTracePath {\n            log(\"⚠️ HotReloading could find log directory from: \\(swiftTracePath)\")\n            builder.derivedLogs = nil // let FileWatcher find logs\n        }\n        #endif\n        signal(SIGPIPE, { _ in print(APP_PREFIX+\"⚠️ SIGPIPE\") })\n        builder.signer = { _ in\n            #if os(tvOS)\n            let dylib = builder.tmpfile+\".dylib\"\n            let codesign = \"\"\"\n                (export CODESIGN_ALLOCATE=\\\"\\(builder.xcodeDev\n                 )/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\\\"; \\\n                if /usr/bin/file \\\"\\(dylib)\\\" | /usr/bin/grep ' shared library ' >/dev/null; \\\n                then /usr/bin/codesign --force -s - \\\"\\(dylib)\\\";\\\n                else exit 1; fi) >>/tmp/hot_reloading.log 2>&1\n                \"\"\"\n            return builder.shell(command: codesign)\n            #else\n            return true\n            #endif\n        }\n        builder.debug = { (what: Any...) in\n            //print(\"\\(APP_PREFIX)***** %@\", what.map {\"\\($0)\"}.joined(separator: \" \"))\n        }\n        builder.forceUnhide = { builder.startUnhide() }\n        builder.bazelLight = true\n\n        let home = NSHomeDirectory()\n            .replacingOccurrences(of: #\"(/Users/[^/]+).*\"#, with: \"$1\",\n            options: .regularExpression)\n        setenv(\"USER_HOME\", home, 1)\n\n        var dirs = [home]\n        let library = home+\"/Library\"\n        if let extra = getenv(INJECTION_DIRECTORIES) {\n            dirs = String(cString: extra).components(separatedBy: \",\")\n                .map { $0[#\"^~\"#, substitute: home] } // expand ~ in paths\n            if builder.derivedLogs == nil && dirs.allSatisfy({\n                $0 != home && !$0.hasPrefix(library) }) {\n                log(\"⚠️ INJECTION_DIRECTORIES should contain ~/Library\")\n                dirs.append(library)\n            }\n        }\n\n        var lastInjected = [String: TimeInterval]()\n\n        if getenv(INJECTION_REPLAY) != nil {\n            injectionQueue.sync {\n                _ = SwiftInjection.replayInjections()\n            }\n        }\n\n        let isVapor = injectionQueue != .main\n        let holdOff = 1.0, minInterval = 0.33 // seconds\n        let firstInjected = Date.timeIntervalSinceReferenceDate + holdOff\n        watchers.append(FileWatcher(roots: dirs,\n                                    callback: { filesChanged, idePath in\n            #if canImport(UIKit) && !os(watchOS)\n            if UIApplication.shared.applicationState != .active { return }\n            #endif\n            builder.lastIdeProcPath = idePath\n            if builder.derivedLogs == nil {\n                if let lastBuilt = FileWatcher.derivedLog {\n                    builder.derivedLogs = URL(fileURLWithPath: lastBuilt)\n                        .deletingLastPathComponent().path\n                    self.log(\"Using logs: \\(lastBuilt).\")\n                } else {\n                    self.log(\"⚠️ Build log for project not found. \" +\n                             \"Please edit a file and build it.\")\n                    return\n                }\n            }\n\n            for changed in filesChanged {\n                guard let changed = changed as? String,\n                      !changed.hasPrefix(library) && !changed.contains(\"/.\"),\n                      Date.timeIntervalSinceReferenceDate -\n                        lastInjected[changed, default: firstInjected] >\n                        minInterval else {\n                    continue\n                }\n                if changed.hasSuffix(\".storyboard\") ||\n                    changed.hasSuffix(\".xib\") {\n                    #if os(iOS) || os(tvOS)\n                    if !NSObject.injectUI(changed) {\n                        self.log(\"⚠️ Interface injection failed\")\n                    }\n                    #endif\n                } else {\n                    SwiftInjection.inject(classNameOrFile: changed)\n                }\n                lastInjected[changed] = Date.timeIntervalSinceReferenceDate\n            }\n        }, runLoop: isVapor ? CFRunLoopGetCurrent() : nil))\n\n        log(\"Standalone \\(APP_NAME) available for sources under \\(dirs)\")\n        if #available(iOS 14.0, tvOS 14.0, *) {\n        } else {\n            log(\"ℹ️ HotReloading not available on Apple Silicon before iOS 14.0\")\n        }\n        if let executable = Bundle.main.executablePath {\n            builder.createUnhider(executable: executable,\n                                  SwiftInjection.objcClassRefs,\n                                  SwiftInjection.descriptorRefs)\n        }\n\n        Self.singleton = self\n        if isVapor {\n            CFRunLoopRun()\n        }\n    }\n\n    var swiftTracing: String?\n\n    func maybeTrace() {\n        if let pattern = getenv(INJECTION_TRACE)\n            .flatMap({String(cString: $0)}), pattern != swiftTracing {\n            SwiftTrace.typeLookup = getenv(INJECTION_LOOKUP) != nil\n            SwiftInjection.traceInjection = true\n            if pattern != \"\" {\n                // This alone will not work for non-final class methods.\n                _ = SwiftTrace.interpose(aBundle: searchBundleImages(),\n                                         methodName: pattern)\n            }\n            swiftTracing = pattern\n        }\n    }\n}\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/SwiftEval.swift",
    "content": "//\n//  SwiftEval.swift\n//  InjectionBundle\n//\n//  Created by John Holdsworth on 02/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/SwiftEval.swift#302 $\n//\n//  Basic implementation of a Swift \"eval()\" including the\n//  mechanics of recompiling a class and loading the new\n//  version used in the associated injection version.\n//  Used as the basis of a new version of Injection.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#if arch(x86_64) || arch(i386) || arch(arm64) // simulator/macOS only\nimport Foundation\n#if SWIFT_PACKAGE\n@_exported import HotReloadingGuts\n#elseif !INJECTION_III_APP\nprivate let APP_PREFIX = \"💉 \",\n    INJECTION_DERIVED_DATA = \"INJECTION_DERIVED_DATA\"\n#endif\n\n#if !INJECTION_III_APP\n#if canImport(SwiftTraceD)\nimport SwiftTraceD\n#endif\n\n@objc protocol SwiftEvalImpl {\n    @objc optional func evalImpl(_ptr: UnsafeMutableRawPointer)\n}\n\nextension NSObject {\n\n    private static var lastEvalByClass = [String: String]()\n\n    @objc public func swiftEval(code: String) -> Bool {\n        if let closure = swiftEval(\"{\\n\\(code)\\n}\", type: (() -> ())?.self) {\n            closure()\n            return true\n        }\n        return false\n    }\n\n    /// eval() for String value\n    @objc public func swiftEvalString(contents: String) -> String {\n        return swiftEval(\"\"\"\n            \"\\(contents)\"\n            \"\"\", type: String.self)\n    }\n\n    /// eval() for value of any type\n    public func swiftEval<T>(_ expression: String, type: T.Type) -> T {\n        let oldClass: AnyClass = object_getClass(self)!\n        let className = \"\\(oldClass)\"\n        let extra = \"\"\"\n\n            extension \\(className) {\n\n                @objc func evalImpl(_ptr: UnsafeMutableRawPointer) {\n                    func xprint<T>(_ str: T) {\n                        if let xprobe = NSClassFromString(\"Xprobe\") {\n                            #if swift(>=4.0)\n                            _ = (xprobe as AnyObject).perform(Selector((\"xlog:\")), with: \"\\\\(str)\")\n                            #elseif swift(>=3.0)\n                            Thread.detachNewThreadSelector(Selector((\"xlog:\")), toTarget:xprobe, with:\"\\\\(str)\" as NSString)\n                            #else\n                            NSThread.detachNewThreadSelector(Selector(\"xlog:\"), toTarget:xprobe, withObject:\"\\\\(str)\" as NSString)\n                            #endif\n                        }\n                    }\n\n                    #if swift(>=3.0)\n                    struct XprobeOutputStream: TextOutputStream {\n                        var out = \"\"\n                        mutating func write(_ string: String) {\n                            out += string\n                        }\n                    }\n\n                    func xdump<T>(_ arg: T) {\n                        var stream = XprobeOutputStream()\n                        dump(arg, to: &stream)\n                        xprint(stream.out)\n                    }\n                    #endif\n\n                    let _ptr = _ptr.assumingMemoryBound(to: (\\(type)).self)\n                    _ptr.pointee = \\(expression)\n                }\n            }\n\n            \"\"\"\n\n        // update evalImpl to implement expression\n\n        if NSObject.lastEvalByClass[className] != expression {\n            do {\n                let tmpfile = try SwiftEval.instance.rebuildClass(oldClass: oldClass,\n                                            classNameOrFile: className, extra: extra)\n                if let newClass = try SwiftEval.instance\n                    .loadAndInject(tmpfile: tmpfile, oldClass: oldClass).first {\n                    if NSStringFromClass(newClass) != NSStringFromClass(oldClass) {\n                        NSLog(\"Class names different. Have the right class been loaded?\")\n                    }\n\n                    // swizzle new version of evalImpl onto class\n\n                    let selector = #selector(SwiftEvalImpl.evalImpl(_ptr:))\n                    if let newMethod = class_getInstanceMethod(newClass, selector) {\n                        class_replaceMethod(oldClass, selector,\n                                            method_getImplementation(newMethod),\n                                            method_getTypeEncoding(newMethod))\n                        NSObject.lastEvalByClass[className] = expression\n                    }\n                }\n            }\n            catch {\n            }\n        }\n\n        // call patched evalImpl to realise expression\n\n        let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)\n        bzero(ptr, MemoryLayout<T>.size)\n        if NSObject.lastEvalByClass[className] == expression {\n            unsafeBitCast(self, to: SwiftEvalImpl.self).evalImpl?(_ptr: ptr)\n        }\n        let out = ptr.pointee\n        ptr.deallocate()\n        return out\n    }\n}\n#endif\n\nextension StringProtocol {\n    subscript(range: NSRange) -> String? {\n        return Range(range, in: String(self)).flatMap { String(self[$0]) }\n    }\n    func escaping(_ chars: String = \"' {}()&*\",\n                  with template: String = \"\\\\$0\") -> String {\n        return self.replacingOccurrences(of: \"[\\(chars)]\",\n            with: template.replacingOccurrences(of: #\"\\\"#, with: \"\\\\\\\\\"),\n            options: [.regularExpression])\n    }\n    func unescape() -> String {\n        return replacingOccurrences(of: #\"\\\\(.)\"#, with: \"$1\",\n                                    options: .regularExpression)\n    }\n}\n\n@objc(SwiftEval)\npublic class SwiftEval: NSObject {\n\n    static var instance = SwiftEval()\n    static let bundleLink = \"/tmp/injection.link\"\n\n    @objc public class func sharedInstance() -> SwiftEval {\n        return instance\n    }\n\n    @objc public var signer: ((_: String) -> Bool)?\n    @objc public var vaccineEnabled: Bool = false\n\n    // client specific info\n    @objc public var frameworks = Bundle.main.privateFrameworksPath\n                                    ?? Bundle.main.bundlePath + \"/Frameworks\"\n    #if arch(arm64)\n    @objc public var arch = \"arm64\"\n    #elseif arch(x86_64)\n    @objc public var arch = \"x86_64\"\n    #else\n    @objc public var arch = \"i386\"\n    #endif\n\n    var forceUnhide = {}\n    var legacyUnhide = false\n    var objectUnhider: ((String) -> Void)?\n    var linkerOptions = \"\"\n\n    let bazelWorkspace = \"IGNORE_WORKSPACE\"\n    let skipBazelLinking = \"--skip_linking\"\n    var bazelLight = getenv(\"INJECTION_BAZEL\") != nil ||\n            UserDefaults.standard.bool(forKey: \"bazelLight\")\n    var moduleLibraries = Set<String>()\n\n    /// Additional logging to /tmp/hot\\_reloading.log for \"HotReloading\" version of injection.\n    var debug = { (what: Any...) in\n        if getenv(\"INJECTION_DEBUG\") != nil || getenv(\"DERIVED_LOGS\") != nil {\n            NSLog(\"\\(APP_PREFIX)***** %@\", what.map {\"\\($0)\"}.joined(separator: \" \"))\n        }\n    }\n\n    // Xcode related info\n    @objc public var xcodeDev = \"/Applications/Xcode.app/Contents/Developer\" {\n        willSet(newValue) {\n            if newValue != xcodeDev {\n                print(APP_PREFIX+\"Selecting Xcode \\(newValue)\")\n            }\n        }\n    }\n\n    @objc public var projectFile: String?\n    @objc public var derivedLogs: String?\n    @objc public var tmpDir = \"/tmp\" {\n        didSet {\n//            SwiftEval.buildCacheFile = \"\\(tmpDir)/eval_builds.plist\"\n        }\n    }\n    @objc public var injectionNumber = 100\n    @objc public var lastIdeProcPath = \"\"\n\n    var tmpfile: String { URL(fileURLWithPath: tmpDir)\n        .appendingPathComponent(\"eval\\(injectionNumber)\").path }\n    var logfile: String { \"\\(tmpfile).log\" }\n    var cmdfile: String { URL(fileURLWithPath: tmpDir)\n        .appendingPathComponent(\"command.sh\").path\n    }\n\n    /// Error handler\n    @objc public var evalError = {\n        (_ message: String) -> Error in\n        print(APP_PREFIX+(message.hasPrefix(\"Compiling\") ?\"\":\"⚠️ \")+message)\n        return NSError(domain: \"SwiftEval\", code: -1,\n                       userInfo: [NSLocalizedDescriptionKey: message])\n    }\n\n    func scriptError(_ what: String) -> Error {\n        var log = (try? String(contentsOfFile: logfile)) ??\n                    \"Could not read log file '\\(logfile)'\"\n        if log.contains(\".h' file not found\") {\n            log += \"\\(APP_PREFIX)⚠️ Adjust the \\\"Header Search Paths\\\" in your project's Build Settings\"\n        }\n        return evalError(\"\"\"\n            \\(what) failed (see: \\(cmdfile))\n            \\(log)\n            \"\"\")\n    }\n\n    var compileByClass = [String: (String, String)]()\n\n    static let simulatorCacheFile = \"/tmp/iOS_Simulator_builds.plist\"\n\n    #if os(macOS) || targetEnvironment(macCatalyst)\n    var buildCacheFile = \"/tmp/macOS_builds.plist\"\n    #elseif os(tvOS)\n    var buildCacheFile = \"/tmp/tvOS_builds.plist\"\n    #elseif os(visionOS)\n    var buildCacheFile = \"/tmp/xrOS_builds.plist\"\n    #elseif targetEnvironment(simulator)\n    var buildCacheFile = SwiftEval.simulatorCacheFile\n    #else\n    var buildCacheFile = \"/tmp/iOS_builds.plist\"\n    #endif\n    lazy var longTermCache =\n        NSMutableDictionary(contentsOfFile: buildCacheFile) ?? NSMutableDictionary()\n\n    public func determineEnvironment(classNameOrFile: String) throws -> (URL, URL) {\n        // Largely obsolete section used find Xcode paths from source file being injected.\n\n        let sourceURL = URL(fileURLWithPath:\n            classNameOrFile.hasPrefix(\"/\") ? classNameOrFile : #file)\n        debug(\"Project file:\", projectFile ?? \"nil\")\n        guard let derivedData = getenv(INJECTION_DERIVED_DATA).flatMap({\n            let url = URL(fileURLWithPath: String(cString: $0))\n            guard FileManager.default.fileExists(atPath: url.path) else {\n                _ = evalError(\"Invalid path in \\(INJECTION_DERIVED_DATA): \"+url.path)\n                return nil }\n            return url }) ??\n            findDerivedData(url: URL(fileURLWithPath: NSHomeDirectory())) ??\n            (projectFile == nil ? findDerivedData(url: sourceURL) :\n                findDerivedData(url: URL(fileURLWithPath: projectFile!))) else {\n            throw evalError(\"\"\"\n                Could not locate derived data. Is the project under your \\\n                home directory? If you are using a custom derived data path, \\\n                add it as an environment variable \\(INJECTION_DERIVED_DATA) \\\n                in your scheme.\n                \"\"\")\n        }\n        debug(\"DerivedData:\", derivedData.path)\n        guard let (projectFile, logsDir) =\n                derivedLogs.flatMap({\n                    (findProject(for: sourceURL, derivedData:derivedData)?\n                        .projectFile ?? URL(fileURLWithPath: \"/tmp/x.xcodeproj\"),\n                     URL(fileURLWithPath: $0)) }) ??\n                projectFile\n                    .flatMap({ logsDir(project: URL(fileURLWithPath: $0), derivedData: derivedData) })\n                    .flatMap({ (URL(fileURLWithPath: projectFile!), $0) }) ??\n                findProject(for: sourceURL, derivedData: derivedData) else {\n                    throw evalError(\"\"\"\n                        Could not locate containing project or it's logs.\n                        For a macOS app you need to turn off the App Sandbox.\n                        Are using a custom DerivedData path? This is not supported.\n                        \"\"\")\n        }\n\n        if false == (try? String(contentsOf: projectFile\n            .appendingPathComponent(\"project.pbxproj\")))?.contains(\"-interposable\") {\n            print(APP_PREFIX+\"\"\"\n                ⚠️ Project file \\(projectFile.path) does not contain the -interposable \\\n                linker flag. In order to be able to inject methods of structs and final \\\n                classes, please add \\\"Other Linker Flags\\\" -Xlinker -interposable for Debug builds only.\n                \"\"\")\n        }\n\n        return (projectFile, logsDir)\n    }\n\n    public func actualCase(path: String) -> String? {\n        let fm = FileManager.default\n        if fm.fileExists(atPath: path) {\n            return path\n        }\n        var out = \"\"\n        for component in path.split(separator: \"/\") {\n            var real: String?\n            if fm.fileExists(atPath: out+\"/\"+component) {\n                real = String(component)\n            } else {\n                guard let contents = try? fm.contentsOfDirectory(atPath: \"/\"+out) else {\n                    return nil\n                }\n                real = contents.first { $0.lowercased() == component.lowercased() }\n            }\n\n            guard let found = real else {\n                return nil\n            }\n            out += \"/\" + found\n        }\n        return out\n    }\n\n    let detectFilepaths = try! NSRegularExpression(pattern: #\"(/(?:[^\\ ]*\\\\.)*[^\\ ]*) \"#)\n\n    @objc public func rebuildClass(oldClass: AnyClass?,\n        classNameOrFile: String, extra: String?) throws -> String {\n        let (projectFile, logsDir) = try\n            determineEnvironment(classNameOrFile: classNameOrFile)\n        let projectRoot = projectFile.deletingLastPathComponent().path\n//        if self.projectFile == nil { self.projectFile = projectFile.path }\n\n        // locate compile command for class\n\n        injectionNumber += 1\n\n        if projectFile.lastPathComponent == bazelWorkspace,\n            let dylib = try bazelLight(projectRoot: projectRoot,\n                                       recompile: classNameOrFile) {\n            return dylib\n        }\n\n        guard var (compileCommand, sourceFile) = try\n            compileByClass[classNameOrFile] ??\n            (longTermCache[classNameOrFile] as? String)\n                .flatMap({ ($0, classNameOrFile) }) ??\n            findCompileCommand(logsDir: logsDir,\n               classNameOrFile: classNameOrFile, tmpfile: tmpfile) else {\n            throw evalError(\"\"\"\n                Could not locate compile command for \"\\(classNameOrFile)\" in \\\n                \\(logsDir.path)/.\\nThis could be due to one of the following:\n                1. Injection does not work with Whole Module Optimization.\n                2. There are restrictions on characters allowed in paths.\n                3. File paths in the simulator are case sensitive.\n                4. The modified source file is not in the current project.\n                5. The source file is an XCTest that has not been run yet.\n                6. Xcode has removed the build logs. Edit a file and re-run \\\n                or try a build clean then rebuild to make logs available or \\\n                consult: \"\\(cmdfile)\".\n                7. If you're using Xcode 16.3+, Swift compilation details are no \\\n                longer logged by default. See the note in the project README. \\\n                You'll need to add a EMIT_FRONTEND_COMMAND_LINES custom \\\n                build setting to your project to continue using InjectionIII.\n                Whatever the problem, if you see this error it may be worth \\\n                trying the start-over implementation https://github.com/johnno1962/InjectionNext.\n                \"\"\")\n        }\n        sourceFile += \"\" // remove warning\n\n        #if targetEnvironment(simulator)\n        // Normalise paths in compile command with the actual casing\n        // of files as the simulator has a case-sensitive file system.\n        for filepath in detectFilepaths.matches(in: compileCommand, options: [],\n            range: NSMakeRange(0, compileCommand.utf16.count))\n            .compactMap({ compileCommand[$0.range(at: 1)] }) {\n            let unescaped = filepath.unescape()\n            if let normalised = actualCase(path: unescaped) {\n                let escaped = normalised.escaping(\"' ${}()&*~\")\n                if filepath != escaped {\n                    print(\"\"\"\n                            \\(APP_PREFIX)Mapped: \\(filepath)\n                            \\(APP_PREFIX)... to: \\(escaped)\n                            \"\"\")\n                    compileCommand = compileCommand\n                        .replacingOccurrences(of: filepath, with: escaped,\n                                              options: .caseInsensitive)\n                }\n            }\n        }\n        #endif\n\n        // load and patch class source if there is an extension to add\n\n        let filemgr = FileManager.default, backup = sourceFile + \".tmp\"\n        if extra != nil {\n            guard var classSource = try? String(contentsOfFile: sourceFile) else {\n                throw evalError(\"Could not load source file \\(sourceFile)\")\n            }\n\n            let changesTag = \"// extension added to implement eval\"\n            classSource = classSource.components(separatedBy: \"\\n\\(changesTag)\\n\")[0] + \"\"\"\n\n                \\(changesTag)\n                \\(extra!)\n\n                \"\"\"\n\n            debug(classSource)\n\n            // backup original and compile patched class source\n\n            if !filemgr.fileExists(atPath: backup) {\n                try! filemgr.moveItem(atPath: sourceFile, toPath: backup)\n            }\n            try! classSource.write(toFile: sourceFile, atomically: true, encoding: .utf8)\n        }\n\n        defer {\n            if extra != nil {\n                try! filemgr.removeItem(atPath: sourceFile)\n                try! filemgr.moveItem(atPath: backup, toPath: sourceFile)\n            }\n        }\n\n        // Extract object path (overidden in UnhidingEval.swift for Xcode 13)\n        let objectFile = xcode13Fix(sourceFile: sourceFile,\n                                    compileCommand: &compileCommand)\n\n        _ = evalError(\"Compiling \\(sourceFile)\")\n\n        let isBazelCompile = compileCommand.contains(skipBazelLinking)\n        if isBazelCompile, arch == \"x86_64\", !compileCommand.contains(arch) {\n            compileCommand = compileCommand\n                .replacingOccurrences(of: #\"(--cpu=ios_)\\w+\"#,\n                  with: \"$1\\(arch)\", options: .regularExpression)\n        }\n\n        if !sourceFile.hasSuffix(\".swift\") {\n            compileCommand += \" -Xclang -fno-validate-pch\"\n        }\n\n        debug(\"Final command:\", compileCommand, \"-->\", objectFile)\n        guard shell(command: \"\"\"\n                (cd \"\\(projectRoot.escaping(\"$\"))\" && \\\n                \\(compileCommand) >\\\"\\(logfile)\\\" 2>&1)\n                \"\"\") || isBazelCompile else {\n            if longTermCache[classNameOrFile] != nil {\n                updateLongTermCache(remove: classNameOrFile)\n                do {\n                    return try rebuildClass(oldClass: oldClass,\n                                     classNameOrFile: classNameOrFile, extra: extra)\n                } catch {\n                    #if true || !os(macOS)\n                    _ = evalError(\"Recompilation failing, are you renaming/adding \" +\n                        \"files? Build your project to generate a new Xcode build \" +\n                        \"log and try injecting again or relauch your app.\")\n                    throw error\n                    #else\n                    // Retry again with new build log in case of added/renamed files...\n                    _ = evalError(\"Compilation failed, rebuilding \\(projectFile.path)\")\n                    _ = shell(command: \"\"\"\n                        /usr/bin/osascript -e 'tell application \"Xcode\"\n                            set targetProject to active workspace document\n                            if (build targetProject) is equal to \"Build succeeded\" then\n                            end if\n                        end tell'\n                        \"\"\")\n                    return try rebuildClass(oldClass: oldClass,\n                                     classNameOrFile: classNameOrFile, extra: extra)\n                    #endif\n                }\n            }\n            throw scriptError(\"Re-compilation\")\n        }\n\n        compileByClass[classNameOrFile] = (compileCommand, sourceFile)\n        if longTermCache[classNameOrFile] as? String != compileCommand &&\n            classNameOrFile.hasPrefix(\"/\") {//&& scanTime > slowLogScan {\n            longTermCache[classNameOrFile] = compileCommand\n            updateLongTermCache()\n        }\n\n        if isBazelCompile {\n            let projectRoot = objectFile // returned by xcode13Fix()\n            return try bazelLink(in: projectRoot, since: sourceFile,\n                                 compileCommand: compileCommand)\n        }\n\n        // link resulting object file to create dynamic library\n        _ = objectUnhider?(objectFile)\n\n        var speclib = \"\"\n        if sourceFile.contains(\"Spec.\") && (try? String(\n            contentsOfFile: classNameOrFile))?.contains(\"Quick\") == true {\n            speclib = logsDir.path+\"/../../Build/Products/\"+Self.quickFiles\n        }\n\n        try link(dylib: \"\\(tmpfile).dylib\", compileCommand: compileCommand,\n                 contents: \"\\\"\\(objectFile)\\\" \\(speclib)\")\n        return tmpfile\n    }\n\n    func updateLongTermCache(remove: String? = nil) {\n        if let source = remove {\n            compileByClass.removeValue(forKey: source)\n            longTermCache.removeObject(forKey: source)\n//            longTermCache.removeAllObjects()\n//            compileByClass.removeAll()\n        }\n        longTermCache.write(toFile: buildCacheFile,\n                            atomically: false)\n    }\n\n    // Implementations provided in UnhidingEval.swift\n    func bazelLight(projectRoot: String, recompile sourceFile: String) throws -> String? {\n        throw evalError(\"No bazel support\")\n    }\n\n    func bazelLink(in projectRoot: String, since sourceFile: String,\n                   compileCommand: String) throws -> String {\n        throw evalError(\"No bazel support\")\n    }\n\n    static let quickFiles = getenv(\"INJECTION_QUICK_FILES\").flatMap {\n        String(cString: $0) } ?? \"Debug-*/{Quick*,Nimble,Cwl*}.o\"\n    static let quickDylib = \"_spec.dylib\"\n    static let dylibDelim = \"===\"\n    static let parsePlatform = try! NSRegularExpression(pattern:\n        #\"-(?:isysroot|sdk)(?: |\"\\n\")((\\#(fileNameRegex)/Contents/Developer)/Platforms/(\\w+)\\.platform\\#(fileNameRegex)\\#\\.sdk)\"#)\n\n    func link(dylib: String, compileCommand: String, contents: String,\n              cd: String = \"\") throws {\n        var platform: String\n        switch buildCacheFile {\n        case Self.simulatorCacheFile: platform = \"iPhoneSimulator\"\n        case \"/tmp/xrOS_builds.plist\": platform = \"XRSimulator\"\n        case \"/tmp/tvOS_builds.plist\": platform = \"AppleTVSimulator\"\n        case \"/tmp/macOS_builds.plist\": platform = \"MacOSX\"\n        default: platform = \"iPhoneOS\"\n        }\n        var sdk = \"\\(xcodeDev)/Platforms/\\(platform).platform/Developer/SDKs/\\(platform).sdk\"\n        if let match = Self.parsePlatform.firstMatch(in: compileCommand,\n            options: [], range: NSMakeRange(0, compileCommand.utf16.count)) {\n            func extract(group: Int, into: inout String) {\n                if let range = Range(match.range(at: group), in: compileCommand) {\n                    into = compileCommand[range]\n                        .replacingOccurrences(of: #\"\\\\(.)\"#, with: \"$1\",\n                                              options: .regularExpression)\n                }\n            }\n            extract(group: 1, into: &sdk)\n            extract(group: 2, into: &xcodeDev)\n            extract(group: 4, into: &platform)\n        } else if compileCommand.contains(\".swift \") {\n            _ = evalError(\"Unable to parse SDK from: \\(compileCommand)\")\n        }\n\n        var osSpecific = \"\"\n        switch platform {\n        case \"iPhoneSimulator\":\n            osSpecific = \"-mios-simulator-version-min=9.0\"\n        case \"iPhoneOS\":\n            osSpecific = \"-miphoneos-version-min=9.0\"\n        case \"AppleTVSimulator\":\n            osSpecific = \"-mtvos-simulator-version-min=9.0\"\n        case \"AppleTVOS\":\n            osSpecific = \"-mtvos-version-min=9.0\"\n        case \"MacOSX\":\n            let target = compileCommand\n                .replacingOccurrences(of: #\"^.*( -target \\S+).*$\"#,\n                                      with: \"$1\", options: .regularExpression)\n            osSpecific = \"-mmacosx-version-min=10.11\"+target\n        case \"XRSimulator\": fallthrough case \"XROS\":\n            osSpecific = \"\"\n        #if os(watchOS)\n        case \"WatchSimulator\":\n            osSpecific = \"\"\n        #endif\n        default:\n            _ = evalError(\"Invalid platform \\(platform)\")\n            // -Xlinker -bundle_loader -Xlinker \\\"\\(Bundle.main.executablePath!)\\\"\"\n        }\n\n        let toolchain = xcodeDev+\"/Toolchains/XcodeDefault.xctoolchain\"\n        let cd = cd == \"\" ? \"\" : \"cd \\\"\\(cd)\\\" && \"\n        if cd != \"\" && !contents.contains(arch) {\n            _ = evalError(\"Modified object files \\(contents) not built for architecture \\(arch)\")\n        }\n        guard shell(command: \"\"\"\n            \\(cd)\"\\(toolchain)/usr/bin/clang\" -arch \"\\(arch)\" \\\n                -Xlinker -dylib -isysroot \"__PLATFORM__\" \\\n                -L\"\\(toolchain)/usr/lib/swift/\\(platform.lowercased())\" \\(osSpecific) \\\n                -undefined dynamic_lookup -dead_strip -Xlinker -objc_abi_version \\\n                -Xlinker 2 -Xlinker -interposable\\(linkerOptions) -fobjc-arc \\\n                -fprofile-instr-generate \\(contents) -L \"\\(frameworks)\" -F \"\\(frameworks)\" \\\n                -rpath \"\\(frameworks)\" -o \\\"\\(dylib)\\\" >>\\\"\\(logfile)\\\" 2>&1\n            \"\"\".replacingOccurrences(of: \"__PLATFORM__\", with: sdk)) else {\n            throw scriptError(\"Linking\")\n        }\n\n        // codesign dylib\n\n        if signer != nil {\n            guard dylib.hasSuffix(Self.quickDylib) ||\n                buildCacheFile == Self.simulatorCacheFile ||\n                signer!(\"\\(injectionNumber).dylib\") else {\n                #if SWIFT_PACKAGE\n                throw evalError(\"Codesign failed. Consult /tmp/hot_reloading.log or Console.app\")\n                #else\n                throw evalError(\"Codesign failed, consult Console. If you are using macOS 11+, Please download a new release from https://github.com/johnno1962/InjectionIII/releases\")\n                #endif\n            }\n        }\n        else {\n            #if os(iOS)\n            // have to delegate code signing to macOS \"signer\" service\n            guard (try? String(contentsOf: URL(string: \"http://localhost:8899\\(tmpfile).dylib\")!)) != nil else {\n                throw evalError(\"Codesign failed. Is 'signer' daemon running?\")\n            }\n            #else\n            guard shell(command: \"\"\"\n                export CODESIGN_ALLOCATE=\\(xcodeDev)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; codesign --force -s '-' \"\\(tmpfile).dylib\"\n                \"\"\") else {\n                throw evalError(\"Codesign failed\")\n            }\n            #endif\n        }\n\n        // Rewrite dylib to prevent macOS 10.15+ from quarantining it\n        let url = URL(fileURLWithPath: dylib)\n        let dylib = try Data(contentsOf: url)\n        try FileManager.default.removeItem(at: url)\n        try dylib.write(to: url)\n    }\n\n\n    /// Regex for path argument, perhaps containg escaped spaces\n    static let argumentRegex = #\"[^\\s\\\\]*(?:\\\\.[^\\s\\\\]*)*\"#\n    /// Regex to extract filename base, perhaps containg escaped spaces\n    static let fileNameRegex = #\"/(\\#(argumentRegex))\\.\\w+\"#\n    /// Extract full file path and name either quoted or escaped\n    static let filePathRegex =\n            #\"\"/[^\"]*\\#(fileNameRegex)\"|/\\#(argumentRegex)\"#\n\n    // Overridden in  UnhidingEval.swift\n    func xcode13Fix(sourceFile: String,\n                    compileCommand: inout String) -> String {\n        // Trim off junk at end of compile command\n        if sourceFile.hasSuffix(\".swift\") {\n            compileCommand = compileCommand.replacingOccurrences(\n                of: \" -o (\\(Self.filePathRegex))\",\n                with: \"\", options: .regularExpression)\n                .components(separatedBy: \" -index-system-modules\")[0]\n        } else {\n            compileCommand = compileCommand\n                .components(separatedBy: \" -o \")[0]\n        }\n        if compileCommand.contains(\"/bazel \") {\n            // force ld to fail as it is not needed\n            compileCommand += \" --linkopt=-Wl,\"+skipBazelLinking\n            // return path to workspace instead of object file\n            return compileCommand[#\"^cd \"([^\"]+)\"\"#] ?? \"dir?\"\n        }\n        let objectFile = \"/tmp/injection_\\(injectionNumber).o\"\n        compileCommand += \" -o \"+objectFile\n        unlink(objectFile)\n        return objectFile\n    }\n\n    func createUnhider(executable: String, _ objcClassRefs: NSMutableArray,\n                       _ descriptorRefs: NSMutableArray) {\n    }\n\n    #if !INJECTION_III_APP\n    lazy var loadXCTest: () = {\n        #if targetEnvironment(simulator)\n        #if os(macOS)\n        let sdk = \"MacOSX\"\n        #elseif os(tvOS)\n        let sdk = \"AppleTVSimulator\"\n        #elseif targetEnvironment(simulator)\n        let sdk = \"iPhoneSimulator\"\n        #else\n        let sdk = \"iPhoneOS\"\n        #endif\n\n        let platform = \"\\(xcodeDev)/Platforms/\\(sdk).platform/Developer/\"\n        guard FileManager.default.fileExists(atPath: platform) else { return }\n\n        if dlopen(platform+\"Library/Frameworks/XCTest.framework/XCTest\", RTLD_LAZY) == nil {\n            debug(String(cString: dlerror()))\n        }\n        if dlopen(platform+\"usr/lib/libXCTestSwiftSupport.dylib\", RTLD_LAZY) == nil {\n            debug(String(cString: dlerror()))\n        }\n        #else\n        let copiedFrameworks = Bundle.main.bundlePath+\"/iOSInjection.bundle/Frameworks/\"\n        for fw in [\"XCTestCore\", \"XCUnit\", \"XCUIAutomation\", \"XCTest\"] {\n            if dlopen(copiedFrameworks+fw+\".framework/\\(fw)\", RTLD_LAZY) == nil {\n                debug(String(cString: dlerror()))\n            }\n        }\n        if dlopen(copiedFrameworks+\"libXCTestSwiftSupport.dylib\", RTLD_LAZY) == nil {\n            debug(String(cString: dlerror()))\n        }\n        #endif\n    }()\n\n    lazy var loadTestsBundle: () = {\n        do {\n            guard let testsBundlePath = try testsBundlePath() else {\n                debug(\"Tests bundle wasn't found - did you run the tests target before running the application?\")\n                return\n            }\n            guard let bundle = Bundle(path: testsBundlePath), bundle.load() else {\n                debug(\"Failed loading tests bundle\")\n                return\n            }\n        } catch {\n            debug(\"Error while searching for the tests bundle: \\(error)\")\n            return\n        }\n    }()\n\n    func testsBundlePath() throws -> String? {\n        guard let pluginsDirectory = Bundle.main.path(forResource: \"PlugIns\", ofType: nil) else {\n            return nil\n        }\n\n        let bundlePaths: [String] = try FileManager.default.contentsOfDirectory(atPath: pluginsDirectory)\n            .filter { $0.hasSuffix(\".xctest\") }\n            .map { directoryName in\n                return \"\\(pluginsDirectory)/\\(directoryName)\"\n            }\n        if bundlePaths.count > 1 {\n            debug(\"Found more than one tests bundle, using the first one\")\n        }\n        return bundlePaths.first\n    }\n\n    @objc func loadAndInject(tmpfile: String, oldClass: AnyClass? = nil)\n        throws -> [AnyClass] {\n\n        print(\"\\(APP_PREFIX)Loading .dylib ...\")\n        // load patched .dylib into process with new version of class\n        var dl: UnsafeMutableRawPointer?\n        for dylib in \"\\(tmpfile).dylib\".components(separatedBy: Self.dylibDelim) {\n            if let object = NSData(contentsOfFile: dylib),\n                memmem(object.bytes, object.count, \"XCTest\", 6) != nil ||\n                memmem(object.bytes, object.count, \"Quick\", 5) != nil,\n                object.count != 0 {\n                _ = loadXCTest\n                _ = loadTestsBundle\n            }\n            #if canImport(SwiftTrace) || canImport(SwiftTraceD)\n            dl = fast_dlopen(dylib, RTLD_NOW)\n            #else\n            dl = dlopen(dylib, RTLD_NOW)\n            #endif\n            guard dl != nil else {\n                var error = String(cString: dlerror())\n                if error.contains(\"___llvm_profile_runtime\") {\n                    error += \"\"\"\n                        \\n\\(APP_PREFIX)⚠️ Loading .dylib has failed, try turning off \\\n                        collection of test coverage in your scheme\n                        \"\"\"\n                } else if error.contains(\"ymbol not found\") {\n                    error += \"\"\"\n                        \\n\\(APP_PREFIX)⚠️ Loading .dylib has failed, This is likely \\\n                        because Swift code being injected references a function \\\n                        using a default argument or a member with access control \\\n                        that is too restrictive or perhaps an XCTest that depends on \\\n                        code not normally linked into your application. Rebuilding and \\\n                        re-running your project (without a build clean) can resolve this.\n                        \"\"\"\n                    forceUnhide()\n                } else if error.contains(\"code signature invalid\") {\n                    error += \"\"\"\n                        \\n\\(APP_PREFIX)⚠️ Loading .dylib has failed due to invalid code signing.\n                        \\(APP_PREFIX)Add the following as a Run Script/Build Phase:\n                        defaults write com.johnholdsworth.InjectionIII \"$PROJECT_FILE_PATH\" \"$EXPANDED_CODE_SIGN_IDENTITY\"\n                        \"\"\"\n                } else if error.contains(\"rying to load an unsigned library\") {\n                    error += \"\"\"\n                        \\n\\(APP_PREFIX)⚠️ Loading .dylib in Xcode 15+ requires code signing.\n                        \\(APP_PREFIX)You will need to run the InjectionIII.app\n                        \"\"\"\n                } else if error.contains(\"incompatible platform\") {\n                    error += \"\"\"\n                        \\n\\(APP_PREFIX)⚠️ Clean build folder when switching platform\n                        \"\"\"\n                }\n                throw evalError(\"dlopen() error: \\(error)\")\n            }\n        }\n\n        if oldClass != nil {\n            // find patched version of class using symbol for existing\n\n            var info = Dl_info()\n            guard dladdr(unsafeBitCast(oldClass, to: UnsafeRawPointer.self), &info) != 0 else {\n                throw evalError(\"Could not locate class symbol\")\n            }\n\n            debug(String(cString: info.dli_sname))\n            guard let newSymbol = dlsym(dl, info.dli_sname) else {\n                throw evalError(\"Could not locate newly loaded class symbol\")\n            }\n\n            return [unsafeBitCast(newSymbol, to: AnyClass.self)]\n        }\n        else {\n            // grep out symbols for classes being injected from object file\n\n            return try extractClasses(dl: dl!, tmpfile: tmpfile)\n        }\n    }\n    #endif\n\n    func startUnhide() {\n    }\n\n    // Overridden by SwiftInjectionEval subclass for injection\n    @objc func extractClasses(dl: UnsafeMutableRawPointer,\n                              tmpfile: String) throws -> [AnyClass] {\n        guard shell(command: \"\"\"\n            \\(xcodeDev)/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm \\(tmpfile).o | \\\n            grep -E ' S _OBJC_CLASS_\\\\$_| _(_T0|\\\\$S|\\\\$s).*CN$' | awk '{print $3}' \\\n            >\\(tmpfile).classes\n            \"\"\") else {\n            throw evalError(\"Could not list class symbols\")\n        }\n        guard var classSymbolNames = (try? String(contentsOfFile:\n            \"\\(tmpfile).classes\"))?.components(separatedBy: \"\\n\") else {\n            throw evalError(\"Could not load class symbol list\")\n        }\n        classSymbolNames.removeLast()\n        return Set(classSymbolNames.compactMap {\n            dlsym(dl, String($0.dropFirst())) })\n            .map { unsafeBitCast($0, to: AnyClass.self) }\n    }\n\n    func findCompileCommand(logsDir: URL, classNameOrFile: String, tmpfile: String)\n        throws -> (compileCommand: String, sourceFile: String)? {\n        // path to project can contain spaces and '$&(){}\n        // Objective-C paths can only contain space and '\n        // project file itself can only contain spaces\n        let isFile = classNameOrFile.hasPrefix(\"/\")\n        let sourceRegex = isFile ?\n            #\"\\Q\\#(classNameOrFile)\\E\"# : #\"/\\#(classNameOrFile)\\.\\w+\"#\n        let swiftEscaped = (isFile ? \"\" : #\"[^\"]*?\"#) + sourceRegex.escaping(\"'$\", with: #\"\\E\\\\*$0\\Q\"#)\n        let objcEscaped = (isFile ? \"\" :\n            #\"(?:/(?:[^/\\\\]*\\\\.)*[^/\\\\ ]+)+\"#) + sourceRegex.escaping()\n        var regexp = #\" -(?:primary-file|c(?<!-frontend -c)) (?:\\\\?\"(\\#(swiftEscaped))\\\\?\"|(\\#(objcEscaped))) \"#\n        let sourceName = URL(fileURLWithPath: classNameOrFile).lastPathComponent\n            .replacingOccurrences(of: \"'\", with: \"_\")\n\n//        print(regexp)\n        let swiftpm = projectFile?.hasSuffix(\".swiftpm\") == true ?\n            \" and $line !~ / -module-name App /\" : \"\"\n        #if targetEnvironment(simulator)\n        let actualPath = #\"\"\"\n                my $out = \"/\";\n\n                for my $name (split \"/\", $_[0]) {\n                    my $next = \"$out/$name\";\n                    if (! -f $next) {\n                        opendir my $dh, $out;\n                        while (my $entry = readdir $dh) {\n                            if (uc $name eq uc $entry) {\n                                $next = \"$out/$entry\";\n                                last;\n                            }\n                        }\n                    }\n                    $out = $next;\n                }\n\n                return substr $out, 2;\n            \"\"\"#\n        #else\n        let actualPath = #\"\"\"\n                return $_[0];\n            \"\"\"#\n        #endif\n\n        #if os(watchOS)\n        let filterWatchOS = \"=~ /-watchos/\"\n        #else\n        let filterWatchOS = \"!~ /-watchos/\"\n        #endif\n\n        // messy but fast\n        try #\"\"\"\n                    use JSON::PP;\n                    use English;\n                    use strict;\n\n                    # line separator in Xcode logs\n                    $INPUT_RECORD_SEPARATOR = \"\\r\";\n\n                    # format is gzip\n                    open GUNZIP, \"/usr/bin/gunzip <\\\"$ARGV[0]\\\" 2>/dev/null |\" or die \"gnozip\";\n\n                    sub actualPath {\n                    \\#(actualPath)\n                    }\n\n                    sub recoverFilelist {\n                        my ($filemap) = $_[0] =~ / -output-file-map (\\#(\n                                                Self.argumentRegex)) /;\n                        $filemap =~ s/\\\\//g;\n                        return if ! -s $filemap;\n                        my $file_handle = IO::File->new( \"< $filemap\" )\n                            or die \"Could not open filemap '$filemap'\";\n                        my $json_text = join'', $file_handle->getlines();\n                        return unless index($json_text, '\\#(sourceName)') > 0;\n                        my $json_map = decode_json( $json_text, { utf8  => 1 } );\n                        my $swift_sources = join \"\\n\", map { actualPath($_) } keys %$json_map;\n                        mkdir \"/tmp/filelists\";\n                        my $filelist = '/tmp/filelists/\\#(sourceName)';\n                        unlink $filelist;\n                        my $listfile = IO::File->new( \"> $filelist\" )\n                            or die \"Could not open list file '$filelist'\";\n                        binmode $listfile, ':utf8';\n                        $listfile->print( $swift_sources );\n                        $listfile->close();\n                        return $filelist;\n                    }\n\n                    # grep the log until there is a match\n                    my ($realPath, $command, $filelist);\n                    while (defined (my $line = <GUNZIP>)) {\n                        if ($line =~ /^\\s*cd /) {\n                            $realPath = $line;\n                        }\n                        elsif ($line =~ m@\\#(regexp.escaping(\"\\\"$\")\n                                    .escaping(\"@\", with: #\"\\E\\$0\\Q\"#)\n                            )@oi and $line \\#(filterWatchOS)\n                                 and $line =~ \"\\#(arch)\"\\#(swiftpm)) {\n                            # found compile command..\n                            # may need to recover file list\n                            my ($flarg) = $line =~ / -filelist (\\#(\n                                            Self.argumentRegex))/;\n                            if ($flarg && ! -s $flarg) {\n                                while (defined (my $line2 = <GUNZIP>)) {\n                                    if (my ($fl) = recoverFilelist($line2)) {\n                                        $filelist = $fl;\n                                        last;\n                                    }\n                                }\n                            }\n                            if ($realPath and (undef, $realPath) =\n                                $realPath =~ /cd (\\\"?)(.*?)\\1\\r/) {\n                                $realPath =~ s/\\\\([^\\$])/$1/g;\n                                $line = \"cd \\\"$realPath\\\"; $line\";\n                            }\n                            # find last\n                            $command = $line;\n                            #exit 0;\n                        }\n                        elsif (my ($bazel, $dir) = $line =~ /^Running \"([^\"]+)\".* (?:patching output for workspace root|with project path) at (\"[^\"]+\")/) {\n                            $command = \"cd $dir && $bazel\";\n                            last;\n                        }\n                        elsif (my ($identity, $bundle) = $line =~ m@/usr/bin/codesign --force --sign (\\S+) --entitlements \\#(Self.argumentRegex) .+ (\\#(Self.argumentRegex))@) {\n                            $bundle =~ s/\\\\(.)/$1/g;\n                            unlink \"\\#(Self.bundleLink)\";\n                            symlink $bundle, \"\\#(Self.bundleLink)\";\n                            system \"rm -f ~/Library/Containers/com.johnholdsworth.InjectionIII/Data/Library/Preferences/com.johnholdsworth.InjectionIII.plist\"\n                                if $identity ne \"-\";\n                            system (qw(/usr/bin/env defaults write com.johnholdsworth.InjectionIII),\n                                    '\\#(projectFile?.escaping(\"'\") ?? \"current project\")', $identity);\n                        }\n                        elsif (!$filelist &&\n                                index($line, \" -output-file-map \") > 0 and\n                               my ($fl) = recoverFilelist($line)) {\n                            $filelist = $fl;\n                        }\n                    }\n\n                    if ($command) {\n                        my ($flarg) = $command =~ / -filelist (\\#(\n                                            Self.argumentRegex))/;\n                        if ($flarg && $filelist && ! -s $flarg) {\n                            $command =~ s/( -filelist )(\\#(\n                                Self.argumentRegex)) /$1'$filelist' /;\n                        }\n                        print $command;\n                        exit 0;\n                    }\n                    # class/file not found\n                    exit 1;\n                    \"\"\"#.write(toFile: \"\\(tmpfile).pl\",\n                               atomically: false, encoding: .utf8)\n\n        guard shell(command: \"\"\"\n            # search through build logs, most recent first\n            cp \\(cmdfile) \\(cmdfile).save 2>/dev/null ; \\\n            cd \"\\(logsDir.path.escaping(\"$\"))\" &&\n            for log in `ls -t *.xcactivitylog`; do\n                #echo \"Scanning $log\"\n                /usr/bin/env perl \"\\(tmpfile).pl\" \"$log\" \\\n                >\"\\(tmpfile).sh\" 2>>\"\\(tmpfile).err\" && exit 0\n            done\n            exit 1;\n            \"\"\") else {\n            #if targetEnvironment(simulator)\n            if #available(iOS 14.0, tvOS 14.0, *) {\n            } else {\n                print(APP_PREFIX+\"\"\"\n                    ⚠️ Injection unable to search logs. \\\n                    Try a more recent iOS 14+ simulator \\\n                    or, download a release directly from \\\n                    https://github.com/johnno1962/InjectionIII/releases\n                    \"\"\")\n            }\n            #endif\n            if let log = try? String(contentsOfFile: \"\\(tmpfile).err\"),\n               !log.isEmpty {\n                _ = evalError(\"stderr contains: \"+log)\n            }\n            return nil\n        }\n\n        var compileCommand: String\n        do {\n            compileCommand = try String(contentsOfFile: \"\\(tmpfile).sh\")\n        } catch {\n            throw evalError(\"\"\"\n                Error reading \\(tmpfile).sh, scanCommand: \\(cmdfile)\n                \"\"\")\n        }\n\n//            // escape ( & ) outside quotes\n//            .replacingOccurrences(of: \"[()](?=(?:(?:[^\\\"]*\\\"){2})*[^\\\"]$)\", with: \"\\\\\\\\$0\", options: [.regularExpression])\n            // (logs of new build system escape ', $ and \")\n        debug(\"Found command:\", compileCommand)\n        compileCommand = compileCommand.replacingOccurrences(of:\n                #\"builtin-swift(DriverJob|Task)Execution --|-frontend-parseable-output|\\r$\"#,\n                                  with: \"\", options: .regularExpression)\n//            // remove excess escaping in new build system (no linger necessary)\n//            .replacingOccurrences(of: #\"\\\\([\\\"'\\\\])\"#, with: \"$1\", options: [.regularExpression])\n            // these files may no longer exist\n            .replacingOccurrences(of:\n                #\" -(pch-output-dir|supplementary-output-file-map|index-store-path|Xcc -ivfsstatcache -Xcc) \\#(Self.argumentRegex) \"#,\n                                  with: \" \", options: .regularExpression)\n            // Strip junk with Xcode 16.3 and EMIT_FRONTEND_COMMAND_LINES\n            .replacingOccurrences(of: #\"^(.*?\"; )?\\S*?\"(?=/)\"#,\n                                  with: \"\", options: .regularExpression)\n        debug(\"Replaced command:\", compileCommand)\n\n        if isFile {\n            return (compileCommand, classNameOrFile)\n        }\n\n        // for eval() extract full path to file from compile command\n\n        let fileExtractor: NSRegularExpression\n        regexp = regexp.escaping(\"$\")\n\n        do {\n            fileExtractor = try NSRegularExpression(pattern: regexp, options: [])\n        }\n        catch {\n            throw evalError(\"Regexp parse error: \\(error) -- \\(regexp)\")\n        }\n\n        guard let matches = fileExtractor\n            .firstMatch(in: compileCommand, options: [],\n                        range: NSMakeRange(0, compileCommand.utf16.count)),\n            var sourceFile = compileCommand[matches.range(at: 1)] ??\n                             compileCommand[matches.range(at: 2)] else {\n            throw evalError(\"Could not locate source file \\(compileCommand) -- \\(regexp)\")\n        }\n\n        sourceFile = actualCase(path: sourceFile.unescape()) ?? sourceFile\n        return (compileCommand, sourceFile)\n    }\n\n    func getAppCodeDerivedData(procPath: String) -> String {\n        //Default with current year\n        let derivedDataPath = { (year: Int, pathSelector: String) -> String in\n            \"Library/Caches/\\(year > 2019 ? \"JetBrains/\" : \"\")\\(pathSelector)/DerivedData\"\n        }\n\n        let year = Calendar.current.component(.year, from: Date())\n        let month = Calendar.current.component(.month, from: Date())\n\n        let defaultPath = derivedDataPath(year, \"AppCode\\(month / 4 == 0 ? year - 1 : year).\\(month / 4 + (month / 4 == 0 ? 3 : 0))\")\n\n        var plistPath = URL(fileURLWithPath: procPath)\n        plistPath.deleteLastPathComponent()\n        plistPath.deleteLastPathComponent()\n        plistPath = plistPath.appendingPathComponent(\"Info.plist\")\n\n        guard let dictionary = NSDictionary(contentsOf: plistPath) as? Dictionary<String, Any> else { return defaultPath }\n        guard let jvmOptions = dictionary[\"JVMOptions\"] as? Dictionary<String, Any> else { return defaultPath }\n        guard let properties = jvmOptions[\"Properties\"] as? Dictionary<String, Any> else { return defaultPath }\n        guard let pathSelector: String = properties[\"idea.paths.selector\"] as? String else { return defaultPath }\n\n        let components = pathSelector.replacingOccurrences(of: \"AppCode\", with: \"\").components(separatedBy: \".\")\n        guard components.count == 2 else { return defaultPath }\n\n        guard let realYear = Int(components[0]) else { return defaultPath }\n        return derivedDataPath(realYear, pathSelector)\n    }\n\n    func findDerivedData(url: URL) -> URL? {\n        if url.path == \"/\" {\n            return nil\n        }\n\n        var relativeDirs = [\"DerivedData\", \"build/DerivedData\"]\n        if lastIdeProcPath.lowercased().contains(\"appcode\") {\n            relativeDirs.append(getAppCodeDerivedData(procPath: lastIdeProcPath))\n        } else {\n            relativeDirs.append(\"Library/Developer/Xcode/DerivedData\")\n        }\n        for relative in relativeDirs {\n            let derived = url.appendingPathComponent(relative)\n            if FileManager.default.fileExists(atPath: derived.path) {\n                return derived\n            }\n        }\n\n        return findDerivedData(url: url.deletingLastPathComponent())\n    }\n\n    func findProject(for source: URL, derivedData: URL) -> (projectFile: URL, logsDir: URL)? {\n        let dir = source.deletingLastPathComponent()\n        if dir.path == \"/\" {\n            return nil\n        }\n\n        if bazelLight {\n            let workspaceURL = dir.deletingLastPathComponent()\n                .appendingPathComponent(bazelWorkspace)\n            if FileManager.default.fileExists(atPath: workspaceURL.path) {\n                return (workspaceURL, derivedLogs.flatMap({\n                    URL(fileURLWithPath: $0)}) ?? dir)\n            }\n        }\n\n        var candidate = findProject(for: dir, derivedData: derivedData)\n        if let files =\n                try? FileManager.default.contentsOfDirectory(atPath: dir.path),\n            let project = Self.projects(in: files)?.first,\n            let logsDir = logsDir(project: dir.appendingPathComponent(project), derivedData: derivedData),\n            mtime(logsDir) > candidate.flatMap({ mtime($0.logsDir) }) ?? 0 {\n                candidate = (dir.appendingPathComponent(project), logsDir)\n        }\n\n        return candidate\n    }\n\n    class func projects(in files: [String]) -> [String]? {\n        return names(withSuffix: \".xcworkspace\", in: files) ??\n                names(withSuffix: \".xcodeproj\", in: files) ??\n                names(withSuffix: \"Package.swift\", in: files)\n    }\n\n    class func names(withSuffix ext: String, in files: [String]) -> [String]? {\n        let matches = files.filter { $0.hasSuffix(ext) }\n        return matches.count != 0 ? matches : nil\n    }\n\n    func mtime(_ url: URL) -> time_t {\n        var info = stat()\n        return stat(url.path, &info) == 0 ? info.st_mtimespec.tv_sec : 0\n    }\n\n    func logsDir(project: URL, derivedData: URL) -> URL? {\n        let filemgr = FileManager.default\n        var projectPrefix = project.deletingPathExtension().lastPathComponent\n        if project.lastPathComponent == \"Package.swift\" {\n            projectPrefix = project.deletingLastPathComponent().lastPathComponent\n        }\n        projectPrefix = projectPrefix.replacingOccurrences(of: #\"\\s+\"#, with: \"_\",\n                                    options: .regularExpression, range: nil)\n        let derivedDirs = (try? filemgr\n            .contentsOfDirectory(atPath: derivedData.path)) ?? []\n        let namedDirs = derivedDirs\n            .filter { $0.starts(with: projectPrefix + \"-\") }\n        var possibleDerivedData = (namedDirs.isEmpty ? derivedDirs : namedDirs)\n            .map { derivedData.appendingPathComponent($0 + \"/Logs/Build\") }\n        possibleDerivedData.append(project.deletingLastPathComponent()\n            .appendingPathComponent(\"DerivedData/\\(projectPrefix)/Logs/Build\"))\n        debug(\"Possible DerivedDatas: \\(possibleDerivedData)\")\n\n        // use most recentry modified\n        return possibleDerivedData\n            .filter { filemgr.fileExists(atPath: $0.path) }\n            .sorted { mtime($0) > mtime($1) }\n            .first\n    }\n\n    class func uniqueTypeNames(signatures: [String], exec: (String) -> Void) {\n        var typesSearched = Set<String>()\n\n        for signature in signatures {\n            let parts = signature.components(separatedBy: \".\")\n            if parts.count < 3 {\n                continue\n            }\n            let typeName = parts[1]\n            if typesSearched.insert(typeName).inserted {\n                exec(typeName)\n            }\n        }\n    }\n\n    func shell(command: String) -> Bool {\n        try! command.write(toFile: cmdfile, atomically: false, encoding: .utf8)\n        debug(command)\n\n        #if os(macOS)\n        let task = Process()\n        task.launchPath = \"/bin/bash\"\n        task.arguments = [cmdfile]\n        task.launch()\n        task.waitUntilExit()\n        let status = task.terminationStatus\n        #else\n        let status = runner.run(script: cmdfile)\n        #endif\n        return status == EXIT_SUCCESS\n    }\n\n    #if !os(macOS)\n    lazy var runner = ScriptRunner()\n\n    class ScriptRunner {\n        let commandsOut: UnsafeMutablePointer<FILE>\n        let statusesIn: UnsafeMutablePointer<FILE>\n\n        init() {\n            let ForReading = 0, ForWriting = 1\n            var commandsPipe = [Int32](repeating: 0, count: 2)\n            var statusesPipe = [Int32](repeating: 0, count: 2)\n            pipe(&commandsPipe)\n            pipe(&statusesPipe)\n\n            var envp = [UnsafeMutablePointer<CChar>?](repeating: nil, count: 2)\n            if let home = getenv(\"USER_HOME\") {\n                envp[0] = strdup(\"HOME=\\(home)\")!\n            }\n\n            if fork() == 0 {\n                let commandsIn = fdopen(commandsPipe[ForReading], \"r\")\n                let statusesOut = fdopen(statusesPipe[ForWriting], \"w\")\n                var buffer = [Int8](repeating: 0, count: Int(MAXPATHLEN))\n\n                close(commandsPipe[ForWriting])\n                close(statusesPipe[ForReading])\n                setbuf(statusesOut, nil)\n\n                while let script = fgets(&buffer, Int32(buffer.count), commandsIn) {\n                    script[strlen(script)-1] = 0\n\n                    let pid = fork()\n                    if pid == 0 {\n                        var argv = [UnsafeMutablePointer<Int8>?](repeating: nil, count: 3)\n                        argv[0] = strdup(\"/bin/bash\")!\n                        argv[1] = strdup(script)!\n                        _ = execve(argv[0], &argv, &envp)\n                        fatalError(\"execve() fails \\(String(cString: strerror(errno)))\")\n                    }\n\n                    var status: Int32 = 0\n                    while waitpid(pid, &status, 0) == -1 {}\n                    fputs(\"\\(status)\\n\", statusesOut)\n                }\n\n                exit(0)\n            }\n\n            commandsOut = fdopen(commandsPipe[ForWriting], \"w\")\n            statusesIn = fdopen(statusesPipe[ForReading], \"r\")\n\n            close(commandsPipe[ForReading])\n            close(statusesPipe[ForWriting])\n            setbuf(commandsOut, nil)\n        }\n\n        func run(script: String) -> Int32 {\n            fputs(\"\\(script)\\n\", commandsOut)\n            var buffer = [Int8](repeating: 0, count: 20)\n            fgets(&buffer, Int32(buffer.count), statusesIn)\n            let status = atoi(buffer)\n            return status >> 8 | status & 0xff\n        }\n    }\n    #endif\n\n    #if DEBUG\n    deinit {\n        print(\"\\(self).deinit()\")\n    }\n    #endif\n}\n\n@_silgen_name(\"fork\")\nfunc fork() -> Int32\n@_silgen_name(\"execve\")\nfunc execve(_ __file: UnsafePointer<Int8>!,\n            _ __argv: UnsafePointer<UnsafeMutablePointer<Int8>?>!,\n            _ __envp: UnsafePointer<UnsafeMutablePointer<Int8>?>!) -> Int32\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/SwiftInjection.swift",
    "content": "//\n//  SwiftInjection.swift\n//  InjectionBundle\n//\n//  Created by John Holdsworth on 05/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/SwiftInjection.swift#223 $\n//\n//  Cut-down version of code injection in Swift. Uses code\n//  from SwiftEval.swift to recompile and reload class.\n//\n//  There is a lot of history in this file. Originaly injection for Swift\n//  worked by patching the vtable of non final classes which worked fairly\n//  well but then we discovered \"interposing\" which is a mechanisim used by\n//  the dynamic linker to resolve references to system frameworks that can\n//  be used to rebind symbols at run time if you use the -interposable linker\n//  flag. This meant we were able to support injecting final methods of classes\n//  and methods of structs and enums. The code still updates the vtable though.\n//\n//  A more recent change is to better supprt injection of generic classes\n//  and classes that inherit from generics which causes problems (crashes) in\n//  the Objective-C runtime. As one can't anticipate the specialisation of\n//  a generic in use from the object file (.dylib) alone, the patching of the\n//  vtable has been moved to the being a part of the sweep which means you need\n//  to have a live object of that specialisation for class injection to work.\n//\n//  Support was also added to use injection with projects using \"The Composable\n//  Architecture\" (TCA) though you need to use a modified version of the repo:\n//  https://github.com/thebrowsercompany/swift-composable-architecture/tree/develop\n//\n//  InjectionIII.app now supports injection of class methods, getters and setters\n//  and can maintain the values of top level and static variables when they are\n//  injected instead of their being reinitialised as the object file is reloaded.\n//\n//  Which Swift symbols can be patched or interposed is now centralised and\n//  configurable using the closure SwiftTrace.injectableSymbol which has been\n//  extended to include async functions which, while they can be injected, can\n//  never be traced due to changes in the stack layout when using co-routines.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#if arch(x86_64) || arch(i386) || arch(arm64) // simulator/macOS only\nimport Foundation\n#if SWIFT_PACKAGE\n@_exported import SwiftTraceD\n#else\n@_exported import SwiftTrace\n#endif\n\n#if os(iOS) || os(tvOS)\nimport UIKit\n\nextension UIViewController {\n\n    /// inject a UIView controller and redraw\n    public func injectVC() {\n        injectSelf()\n        for subview in self.view.subviews {\n            subview.removeFromSuperview()\n        }\n        if let sublayers = self.view.layer.sublayers {\n            for sublayer in sublayers {\n                sublayer.removeFromSuperlayer()\n            }\n        }\n        viewDidLoad()\n    }\n}\n#endif\n\nextension NSObject {\n\n    public func injectSelf() {\n        if let oldClass: AnyClass = object_getClass(self) {\n            SwiftInjection.inject(oldClass: oldClass, classNameOrFile: \"\\(oldClass)\")\n        }\n    }\n\n    @objc\n    public class func inject(file: String) {\n        SwiftInjection.inject(classNameOrFile: file)\n    }\n}\n\n@objc(SwiftInjection)\npublic class SwiftInjection: NSObject {\n\n    public typealias SymbolName = UnsafePointer<CChar>\n    @objc static var traceInjection = false\n\n    static let testQueue = DispatchQueue(label: \"INTestQueue\")\n    static let injectedSEL = #selector(SwiftInjected.injected)\n    #if os(iOS) || os(tvOS)\n    static let viewDidLoadSEL = #selector(UIViewController.viewDidLoad)\n    #endif\n    static let notification = Notification.Name(INJECTION_BUNDLE_NOTIFICATION)\n\n    static var injectionDetail = getenv(INJECTION_DETAIL) != nil\n    static let registerClasses = false && SwiftTrace.deviceInjection\n    static var objcClassRefs = NSMutableArray()\n    static var descriptorRefs = NSMutableArray()\n    static var injectedPrefix: String {\n        return \"#\\(SwiftEval.instance.injectionNumber-100)/\"\n    }\n\n    open class func log(_ what: Any...) {\n        print(APP_PREFIX+what.map {\"\\($0)\"}.joined(separator: \" \"))\n    }\n    open class func detail(_ msg: @autoclosure () -> String) {\n        if injectionDetail {\n            log(msg())\n        }\n    }\n\n    @objc\n    open class func inject(oldClass: AnyClass? = nil, classNameOrFile: String) {\n        do {\n            let tmpfile = try SwiftEval.instance.rebuildClass(oldClass: oldClass,\n                                    classNameOrFile: classNameOrFile, extra: nil)\n            try inject(tmpfile: tmpfile)\n        }\n        catch {\n            SwiftEval.instance.updateLongTermCache(remove: classNameOrFile)\n        }\n    }\n\n    @objc\n    open class func replayInjections() -> Int {\n        do {\n            func mtime(_ path: String) -> time_t {\n                return SwiftEval.instance.mtime(URL(fileURLWithPath: path))\n            }\n            let execBuild = mtime(Bundle.main.executablePath!)\n\n            while true {\n                SwiftEval.instance.injectionNumber += 1\n                let tmpfile = SwiftEval.instance.tmpfile\n                if mtime(\"\\(tmpfile).dylib\") < execBuild {\n                    SwiftEval.instance.injectionNumber -= 1\n                    break\n                }\n                try inject(tmpfile: tmpfile)\n            }\n        }\n        catch {\n        }\n        return SwiftEval.instance.injectionNumber\n    }\n\n    open class func versions(of aClass: AnyClass) -> [AnyClass] {\n        var out = [AnyClass](), nc: UInt32 = 0, info = Dl_info()\n        if let classes = UnsafePointer(objc_copyClassList(&nc)) {\n            let named = _typeName(aClass)\n            for i in 0 ..< Int(nc) {\n                if class_getSuperclass(classes[i]) != nil && classes[i] != aClass,\n                   _typeName(classes[i]) == named,\n                   !(registerClasses &&\n                     dladdr(autoBitCast(classes[i]), &info) != 0 &&\n                     strcmp(info.dli_sname, \"injected_code\") == 0) {\n                    out.append(classes[i])\n                }\n            }\n            free(UnsafeMutableRawPointer(mutating: classes))\n        }\n        return out\n    }\n\n    @objc\n    open class func inject(tmpfile: String) throws {\n        try inject(tmpfile: tmpfile, newClasses:\n            SwiftEval.instance.loadAndInject(tmpfile: tmpfile))\n    }\n\n    @objc\n    open class func inject(tmpfile: String, newClasses: [AnyClass]) throws {\n        var totalPatched = 0, totalSwizzled = 0\n        var injectedGenerics = Set<String>()\n        var injectedClasses = [AnyClass]()\n        var sweepClasses = [AnyClass]()\n        var testClasses = [AnyClass]()\n\n        injectionDetail = getenv(INJECTION_DETAIL) != nil\n        SwiftTrace.preserveStatics = getenv(INJECTION_PRESERVE_STATICS) != nil\n        if getenv(INJECTION_TRACE) != nil {\n            traceInjection = true\n            SwiftTrace.typeLookup = true\n        }\n\n        // Determine any generic classes being injected.\n        findSwiftSymbols(searchLastLoaded(), \"CMa\") {\n            accessor, symname, _, _ in\n            if let demangled = SwiftMeta.demangle(symbol: symname),\n               let genericClassName = demangled[safe: (.last(of: \" \")+1)...],\n               !genericClassName.hasPrefix(\"__C.\") {\n                injectedGenerics.insert(genericClassName)\n            }\n        }\n\n        #if !targetEnvironment(simulator) && SWIFT_PACKAGE && canImport(InjectionScratch)\n        if let pseudoImage = lastPseudoImage() {\n            fillinObjcClassMetadata(in: pseudoImage)\n        }\n        #endif\n\n        // First, the old way for non-generics\n        for var newClass: AnyClass in newClasses {\n            let className = _typeName(newClass)\n            detail(\"Processing class \\(className)\")\n            var oldClasses = versions(of: newClass)\n            injectedGenerics.remove(className)\n            if oldClasses.isEmpty {\n                var info = Dl_info()\n                if dladdr(autoBitCast(newClass), &info) != 0,\n                   let symbol = info.dli_sname,\n                   let oldClass = dlsym(SwiftMeta.RTLD_MAIN_ONLY, symbol) {\n                    oldClasses.append(autoBitCast(oldClass))\n                }\n            }\n            sweepClasses += oldClasses\n\n            for var oldClass: AnyClass in oldClasses {\n                let oldClassName = _typeName(oldClass) +\n                    String(format: \" %p\", unsafeBitCast(oldClass, to: uintptr_t.self))\n                #if true\n                let patched = patchSwiftVtable(oldClass: oldClass, newClass: newClass)\n                #else\n                let patched = newPatchSwiftVtable(oldClass: oldClass, tmpfile: tmpfile)\n                #endif\n\n                if patched != 0 {\n                    totalPatched += patched\n\n                    let existingClass = unsafeBitCast(oldClass, to:\n                        UnsafeMutablePointer<SwiftMeta.TargetClassMetadata>.self)\n                    let classMetadata = unsafeBitCast(newClass, to:\n                        UnsafeMutablePointer<SwiftMeta.TargetClassMetadata>.self)\n\n                    // Old mechanism for Swift equivalent of \"Swizzling\".\n                    if classMetadata.pointee.ClassAddressPoint != existingClass.pointee.ClassAddressPoint {\n                        log(\"\"\"\n                            ⚠️ Mixing Xcode versions across injection. This may work \\\n                            but \"Clean Builder Folder\" when switching Xcode versions. \\\n                            To clear the cache: rm \\(SwiftEval.instance.buildCacheFile)\n                            \"\"\")\n                    } else\n                    if classMetadata.pointee.ClassSize != existingClass.pointee.ClassSize {\n                        log(\"\"\"\n                            ⚠️ Adding or [re]moving methods of non-final class \\\n                            \\(oldClass)[\\(existingClass.pointee.ClassSize)],\\\n                            \\(newClass)[\\(classMetadata.pointee.ClassSize)] \\\n                            is not supported. Your application will likely crash. \\\n                            Paradoxically, you can avoid this by making the class \\\n                            you are trying to inject (and add methods to) \"final\". ⚠️\n                            \"\"\")\n                    }\n                }\n\n                // Is there a generic superclass?\n                if inheritedGeneric(anyType: oldClass) {\n                    // fallback to limited processing avoiding objc runtime.\n                    // (object_getClass() and class_copyMethodList() crash)\n                    let swizzled = swizzleBasics(oldClass: oldClass, tmpfile: tmpfile)\n                    totalSwizzled += swizzled\n                    detail(\"Injected class '\\(oldClassName)' (\\(patched),\\(swizzled)).\")\n                    continue\n                }\n\n                if oldClass == newClass {\n                    if oldClasses.count > 1 {\n                        oldClass = oldClasses.first!\n                        newClass = oldClasses.last!\n                    } else {\n                        log(\"⚠️ Could not find versions of class \\(_typeName(newClass)). ⚠️\")\n                    }\n                }\n\n                var swizzled: Int\n                if !SwiftTrace.deviceInjection || registerClasses {\n                // old-school swizzle Objective-C class & instance methods\n                    swizzled = injection(swizzle: object_getClass(oldClass),\n                                         from: object_getClass(newClass)) +\n                               injection(swizzle: oldClass, from: newClass)\n                } else {\n                    #if !targetEnvironment(simulator) && SWIFT_PACKAGE && canImport(InjectionScratch)\n                    swizzled = onDevice(swizzle: oldClass, from: newClass)\n                    #else\n                    swizzled = injection(swizzle: object_getClass(oldClass)!,\n                                         tmpfile: tmpfile) +\n                               injection(swizzle: oldClass, tmpfile: tmpfile)\n                    #endif\n                }\n                totalSwizzled += swizzled\n\n                detail(\"Patched class '\\(oldClassName)' (\\(patched),\\(swizzled))\")\n            }\n\n            if let XCTestCase = objc_getClass(\"XCTestCase\") as? AnyClass,\n                isSubclass(newClass, of: XCTestCase) {\n                testClasses.append(newClass)\n            }\n\n            injectedClasses.append(newClass)\n        }\n\n        #if !SWIFT_PACKAGE\n        let patchedGenerics = hookedPatch(of: injectedGenerics, tmpfile: tmpfile)\n        totalPatched += patchedGenerics.count\n        sweepClasses += patchedGenerics\n        #endif\n\n        // (Reverse) interposing, reducers, operation on a device etc.\n        let totalInterposed = newerProcessing(tmpfile: tmpfile, sweepClasses)\n        lastLoadedImage().symbols(withPrefix: \"__OBJC_$_CATEGORY_\") {_,_,_ in\n            totalSwizzled += 1\n        }\n        if totalPatched + totalSwizzled + totalInterposed + testClasses.count == 0 {\n            log(\"⚠️ Injection may have failed. Have you added -Xlinker -interposable (for the Debug configuration only, without double quotes and on separate lines) to the \\\"Other Linker Flags\\\" of the executable and frameworks? ⚠️\")\n        }\n\n        DispatchQueue.main.async {\n        // Thanks https://github.com/johnno1962/injectionforxcode/pull/234\n        if !testClasses.isEmpty {\n            testQueue.async {\n                testQueue.suspend()\n                let timer = Timer(timeInterval: 0, repeats:false, block: { _ in\n                    for newClass in testClasses {\n                        NSObject.runXCTestCase(newClass)\n                    }\n                    testQueue.resume()\n                })\n                RunLoop.main.add(timer, forMode: RunLoop.Mode.common)\n            }\n        } else { // implement class and instance injected() methods\n            if !SwiftTrace.deviceInjection {\n                typealias ClassIMP = @convention(c) (AnyClass, Selector) -> ()\n                for cls in injectedClasses {\n                    if let classMethod = class_getClassMethod(cls, injectedSEL) {\n                        let classIMP = method_getImplementation(classMethod)\n                        unsafeBitCast(classIMP, to: ClassIMP.self)(cls, injectedSEL)\n                    }\n                }\n            }\n            performSweep(oldClasses: sweepClasses, tmpfile,\n                getenv(INJECTION_OF_GENERICS) != nil ? injectedGenerics : [])\n\n            NotificationCenter.default.post(name: notification, object: sweepClasses)\n        }\n        }\n    }\n\n    open class func isSubclass(_ subClass: AnyClass, of aClass: AnyClass) -> Bool {\n        var subClass: AnyClass? = subClass\n        repeat {\n            if subClass == aClass {\n                return true\n            }\n            subClass = class_getSuperclass(subClass)\n        } while subClass != nil\n        return false\n    }\n\n    open class func inheritedGeneric(anyType: Any.Type) -> Bool {\n        var inheritedGeneric: AnyClass? = anyType as? AnyClass\n        if class_getSuperclass(inheritedGeneric) == nil {\n            return true\n        }\n        while let parent = inheritedGeneric {\n            if _typeName(parent).hasSuffix(\">\") {\n                return true\n            }\n            inheritedGeneric = class_getSuperclass(parent)\n        }\n        return false\n    }\n\n    open class func newerProcessing(tmpfile: String,\n                                    _ sweepClasses: [AnyClass]) -> Int {\n        // new mechanism for injection of Swift functions,\n        // using \"interpose\" API from dynamic loader along\n        // with -Xlinker -interposable \"Other Linker Flags\".\n        let interposed = Set(interpose(functionsIn: \"\\(tmpfile).dylib\"))\n        if interposed.count != 0 {\n            for symname in interposed {\n                detail(\"Interposed \"+describeImageSymbol(symname))\n            }\n            log(\"Interposed \\(interposed.count) function references.\")\n        }\n\n        #if !targetEnvironment(simulator) && SWIFT_PACKAGE && canImport(InjectionScratch)\n        if let pseudoImage = lastPseudoImage() {\n            onDeviceSpecificProcessing(for: pseudoImage, sweepClasses)\n        }\n        #endif\n\n        // Can prevent statics from re-initializing on injection\n        reverseInterposeStaticsAddressors(tmpfile)\n\n        // log any types being injected\n        var ntypes = 0, npreviews = 0\n        findSwiftSymbols(searchLastLoaded(), \"N\") {\n            (typePtr, symbol, _, _) in\n            if let existing: Any.Type =\n                autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, symbol)) {\n                let name = _typeName(existing)\n                if name.hasSuffix(\"_Previews\") {\n                    npreviews += 1\n                }\n                if name.hasSuffix(\"PreviewRegistryfMu_\") {\n                    return\n                }\n                ntypes += 1\n                log(\"Injected type #\\(ntypes) '\\(name)'\")\n                if SwiftTrace.deviceInjection {\n                    SwiftMeta.cloneValueWitness(from: existing, onto: autoBitCast(typePtr))\n                }\n                let newSize = SwiftMeta.sizeof(anyType: autoBitCast(typePtr))\n                if newSize != 0 && newSize != SwiftMeta.sizeof(anyType: existing) {\n                    log(\"⚠️ Size of value type \\(_typeName(existing)) has changed (\\(newSize) != \\(SwiftMeta.sizeof(anyType: existing))). You cannot inject changes to memory layout. This will likely just crash. ⚠️\")\n                }\n            }\n        }\n\n        if false && npreviews > 0 && ntypes > 2 && SwiftTrace.deviceInjection {\n            log(\"⚠️ Device injection may fail if you have more than one type from the injected file referred to in a SwiftUI View.\")\n        }\n\n        if getenv(INJECTION_DYNAMIC_CAST) != nil {\n            // Cater for dynamic cast (i.e. as?) to types that have been injected.\n            DynamicCast.hook_lastInjected()\n        }\n\n        var reducers = [SymbolName]()\n        if !injectableReducerSymbols.isEmpty {\n            reinitializeInjectedReducers(tmpfile, reinitialized: &reducers)\n            let s = reducers.count == 1 ? \"\" : \"s\"\n            log(\"Overrode \\(reducers.count) reducer\"+s)\n        }\n\n        return interposed.count + reducers.count\n    }\n\n    #if true // Original version of vtable patch, headed for retirement..\n    /// Patch entries in vtable of existing class to be that in newly loaded version of class for non-final methods\n    class func patchSwiftVtable(oldClass: AnyClass, newClass: AnyClass) -> Int {\n        // overwrite Swift vtable of existing class with implementations from new class\n        let existingClass = unsafeBitCast(oldClass, to:\n            UnsafeMutablePointer<SwiftMeta.TargetClassMetadata>.self)\n        let classMetadata = unsafeBitCast(newClass, to:\n            UnsafeMutablePointer<SwiftMeta.TargetClassMetadata>.self)\n\n        // Is this a Swift class?\n        // Reference: https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h#L1195\n        let oldSwiftCondition = classMetadata.pointee.Data & 0x1 == 1\n        let newSwiftCondition = classMetadata.pointee.Data & 0x3 != 0\n\n        guard newSwiftCondition || oldSwiftCondition else { return 0 }\n        var patched = 0\n\n        #if true // supplimented by \"interpose\" code\n        // vtable still needs to be patched though for non-final methods\n        func byteAddr<T>(_ location: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<UInt8> {\n            return location.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 }\n        }\n\n        let vtableOffset = byteAddr(&existingClass.pointee.IVarDestroyer) - byteAddr(existingClass)\n\n        #if false\n        // Old mechanism for Swift equivalent of \"Swizzling\".\n        if classMetadata.pointee.ClassSize != existingClass.pointee.ClassSize {\n            log(\"⚠️ Adding or [re]moving methods on non-final classes is not supported. Your application will likely crash. ⚠️\")\n        }\n\n        // original injection implementaion for Swift.\n        let vtableLength = Int(existingClass.pointee.ClassSize -\n            existingClass.pointee.ClassAddressPoint) - vtableOffset\n\n        memcpy(byteAddr(existingClass) + vtableOffset,\n               byteAddr(classMetadata) + vtableOffset, vtableLength)\n        #else\n        // new version only copying only symbols that are functions.\n        let newTable = (byteAddr(classMetadata) + vtableOffset)\n            .withMemoryRebound(to: SwiftTrace.SIMP?.self, capacity: 1) { $0 }\n\n        SwiftTrace.iterateMethods(ofClass: oldClass) {\n            (name, slotIndex, vtableSlot, stop) in\n            if let replacement = SwiftTrace.interposed(replacee:\n                autoBitCast(newTable[slotIndex] ?? vtableSlot.pointee)),\n                autoBitCast(vtableSlot.pointee) != replacement {\n                traceAndReplace(vtableSlot.pointee,\n                    replacement: replacement, name: name) {\n                    (replacement: UnsafeMutableRawPointer) -> String? in\n                    vtableSlot.pointee = autoBitCast(replacement)\n                    if autoBitCast(vtableSlot.pointee) == replacement { ////\n                        patched += 1\n                        return newTable[slotIndex] != nil ? \"Patched\" : \"Populated\"\n                    }\n                    return nil\n                }\n            }\n        }\n        #endif\n        #endif\n        return patched\n    }\n    #endif\n\n    /// Newer way to patch vtable looking up existing entries individually in newly loaded dylib.\n    open class func newPatchSwiftVtable(oldClass: AnyClass,// newClass: AnyClass?,\n                                        tmpfile: String) -> Int {\n        var patched = 0\n\n        SwiftTrace.forEachVTableEntry(ofClass: oldClass) {\n            (symname, slotIndex, vtableSlot, stop) in\n            let existing: UnsafeMutableRawPointer = autoBitCast(vtableSlot.pointee)\n            guard let replacement = fast_dlsym(lastLoadedImage(), symname) ??\n                    (dlsym(SwiftMeta.RTLD_DEFAULT, symname) ??\n                     findSwiftSymbol(searchBundleImages(), symname, .any)).flatMap({\n                    autoBitCast(SwiftTrace.interposed(replacee: $0)) }) else {\n                log(\"⚠️ Class patching failed to lookup \" +\n                    describeImageSymbol(symname))\n                return\n            }\n            if replacement != existing {\n                traceAndReplace(existing, replacement: replacement, symname: symname) {\n                    (replacement: UnsafeMutableRawPointer) -> String? in\n                    vtableSlot.pointee = autoBitCast(replacement)\n                    if autoBitCast(vtableSlot.pointee) == replacement {\n                        patched += 1\n                        return \"Patched\"\n                    }\n                    return nil\n                }\n            }\n        }\n\n        return patched\n    }\n\n    /// Pop a trace on a newly injected method and convert the pointer type while you're at it\n    open class func traceInjected<IN>(replacement: IN, name: String? = nil,\n                                     symname: UnsafePointer<Int8>? = nil,\n                     objcMethod: Method? = nil, objcClass: AnyClass? = nil)\n        -> UnsafeRawPointer {\n        if traceInjection || SwiftTrace.isTracing,\n           let name = name ??\n                symname.flatMap({ SwiftMeta.demangle(symbol: $0) }) ??\n                objcMethod.flatMap({ NSStringFromSelector(method_getName($0)) }),\n           !name.contains(\".unsafeMutableAddressor :\"),\n           let tracer = SwiftTrace.trace(name: injectedPrefix + name,\n                   objcMethod: objcMethod, objcClass: objcClass,\n                   original: autoBitCast(replacement)) {\n            return autoBitCast(tracer)\n        }\n        return autoBitCast(replacement)\n    }\n\n    /// All implementation replacements go through this function which can also apply a trace\n    /// - Parameters:\n    ///   - existing: implementation being replaced\n    ///   - replacement: new implementation\n    ///   - name: demangled symbol for trace\n    ///   - symname: raw symbol nme\n    ///   - objcMethod: used for trace\n    ///   - objcClass: used for trace\n    ///   - apply: closure to apply replacement\n    open class func traceAndReplace<E,O>(_ existing: E,\n                                         replacement: UnsafeRawPointer,\n            name: String? = nil, symname: UnsafePointer<Int8>? = nil,\n            objcMethod: Method? = nil, objcClass: AnyClass? = nil,\n            apply: (O) -> String?) {\n        let traced = traceInjected(replacement: replacement, name: name,\n           symname: symname, objcMethod: objcMethod, objcClass: objcClass)\n\n        // injecting getters returning generics best avoided for some reason.\n        if let getted = name?[safe: .last(of: \".getter : \", end: true)...] {\n            if getted.hasSuffix(\">\") { return }\n            if let type = SwiftMeta.lookupType(named: getted),\n               inheritedGeneric(anyType: type) {\n                return\n            }\n        }\n        if let success = apply(autoBitCast(traced)) {\n            detail(\"\\(success) \\(autoBitCast(existing) as UnsafeRawPointer) -> \\(replacement) \" +    describeImagePointer(replacement))\n        }\n    }\n\n    /// Resolve a perhaps traced function back to name of original symbol\n    open class func originalSym(for existing: UnsafeMutableRawPointer) -> SymbolName? {\n        var info = Dl_info()\n        if fast_dladdr(existing, &info) != 0 {\n            return info.dli_sname\n        } else if let swizzle = SwiftTrace.originalSwizzle(for: autoBitCast(existing)),\n                  fast_dladdr(autoBitCast(swizzle.implementation), &info) != 0 {\n            return info.dli_sname\n        }\n        return nil\n    }\n\n    /// If class is a generic, patch its specialised vtable and basic selectors\n    open class func patchGenerics(oldClass: AnyClass, tmpfile: String,\n                                  injectedGenerics: Set<String>,\n                                  patched: inout Set<UnsafeRawPointer>) -> Bool {\n        if let genericClassName = _typeName(oldClass)[safe: ..<(.first(of: \"<\"))],\n           injectedGenerics.contains(genericClassName) {\n            if patched.insert(autoBitCast(oldClass)).inserted {\n                let patched = newPatchSwiftVtable(oldClass: oldClass, tmpfile: tmpfile)\n                let swizzled = swizzleBasics(oldClass: oldClass, tmpfile: tmpfile)\n                log(\"Injected generic '\\(oldClass)' (\\(patched),\\(swizzled))\")\n            }\n            return oldClass.instancesRespond(to: injectedSEL)\n        }\n        return false\n    }\n\n    @objc(vaccine:)\n    open class func performVaccineInjection(_ object: AnyObject) {\n        #if !os(watchOS)\n        let vaccine = Vaccine()\n        vaccine.performInjection(on: object)\n        #endif\n    }\n\n    #if os(iOS) || os(tvOS)\n    @objc(flash:)\n    open class func flash(vc: UIViewController) {\n        DispatchQueue.main.async {\n            let v = UIView(frame: vc.view.frame)\n            v.backgroundColor = .white\n            v.alpha = 0.3\n            vc.view.addSubview(v)\n            UIView.animate(withDuration: 0.2,\n                           delay: 0.0,\n                           options: UIView.AnimationOptions.curveEaseIn,\n                           animations: {\n                            v.alpha = 0.0\n            }, completion: { _ in v.removeFromSuperview() })\n        }\n    }\n    #endif\n}\n\n@objc\npublic class SwiftInjectionEval: UnhidingEval {\n\n    @objc override open class func sharedInstance() -> SwiftEval {\n        SwiftEval.instance = SwiftInjectionEval()\n        return SwiftEval.instance\n    }\n\n    @objc override open func extractClasses(dl: UnsafeMutableRawPointer,\n                                       tmpfile: String) throws -> [AnyClass] {\n        var classes = [AnyClass]()\n        SwiftTrace.forAllClasses(bundlePath: searchLastLoaded()) {\n            aClass, stop in\n            classes.append(aClass)\n        }\n        if classes.count > 0 && !SwiftTrace.deviceInjection {\n            print(\"\\(APP_PREFIX)Loaded .dylib - Ignore any duplicate class warning ⬆️\")\n        }\n        #if false // Just too dubious\n        // Determine any generic classes being injected.\n        // (Done as part of sweep in the end.)\n        findSwiftSymbols(searchLastLoaded(), \"CMa\") {\n            accessor, _, _, _ in\n            struct Something {}\n            typealias AF = @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> UnsafeRawPointer\n            let tmd: Any.Type = Void.self\n            let tmd0 = unsafeBitCast(tmd, to: UnsafeRawPointer.self)\n            let tmd1 = unsafeBitCast(accessor, to: AF.self)(tmd0, tmd0)\n            let tmd2 = unsafeBitCast(tmd1, to: Any.Type.self)\n            if let genericClass = tmd2 as? AnyClass {\n                classes.append(genericClass)\n            }\n        }\n        #endif\n        return classes\n    }\n}\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/SwiftInterpose.swift",
    "content": "//\n//  SwiftInterpose.swift\n//\n//  Created by John Holdsworth on 25/04/2022.\n//\n//  Interpose processing (-Xlinker -interposable).\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/SwiftInterpose.swift#11 $\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\nextension SwiftInjection {\n\n    /// \"Interpose\" all function definitions in a dylib onto the main executable\n    public class func interpose(functionsIn dylib: String) -> [UnsafePointer<Int8>] {\n        var symbols = [UnsafePointer<Int8>]()\n\n        #if false // DLKit based interposing\n        // ... doesn't play well with tracing.\n        var replacements = [UnsafeMutableRawPointer]()\n\n        // Find all definitions of Swift functions and ...\n        // SwiftUI body properties defined in the new dylib.\n\n        for (symbol, value, _) in DLKit.lastImage\n            .swiftSymbols(withSuffixes: injectableSuffixes) {\n            guard var replacement = value else {\n                continue\n            }\n            let method = symbol.demangled ?? String(cString: symbol)\n            if detail {\n                log(\"Replacing \\(method)\")\n            }\n\n            if traceInjection || SwiftTrace.isTracing, let tracer = SwiftTrace\n                .trace(name: injectedPrefix+method, original: replacement) {\n                replacement = autoBitCast(tracer)\n            }\n\n            symbols.append(symbol)\n            replacements.append(replacement)\n        }\n\n        // Rebind all references in all images in the app bundle\n        // to function symbols defined in the last loaded dylib\n        // to the new implementations in the newly loaded dylib.\n        DLKit.appImages[symbols] = replacements\n\n        #else // Current SwiftTrace based code...\n        let main = dlopen(nil, RTLD_NOW)\n        var interposes = [dyld_interpose_tuple]()\n\n        #if false\n        let suffixesToInterpose = SwiftTrace.traceableFunctionSuffixes\n            // Oh alright, interpose all property getters..\n            .map { $0 == \"Qrvg\" ? \"g\" : $0 }\n            // and class/static members\n            .flatMap { [$0, $0+\"Z\"] }\n        for suffix in suffixesToInterpose {\n            findSwiftSymbols(dylib, suffix) { (loadedFunc, symbol, _, _) in\n                // interposing was here ...\n            }\n        }\n        #endif\n        filterImageSymbols(ST_LAST_IMAGE, .any, SwiftTrace.injectableSymbol) {\n            (loadedFunc, symbol, _, _) in\n            guard let existing = dlsym(main, symbol) ??\n                    findSwiftSymbol(searchBundleImages(), symbol, .any),\n                  existing != loadedFunc/*,\n                let current = SwiftTrace.interposed(replacee: existing)*/ else {\n                return\n            }\n            let current = existing\n            traceAndReplace(current, replacement: loadedFunc, symname: symbol) {\n                (replacement: UnsafeMutableRawPointer) -> String? in\n                interposes.append(dyld_interpose_tuple(replacement: replacement,\n                                                       replacee: current))\n                symbols.append(symbol)\n                return nil //\"Interposing\"\n            }\n            #if ORIGINAL_2_2_0_CODE\n            SwiftTrace.interposed[existing] = loadedFunc\n            SwiftTrace.interposed[current] = loadedFunc\n            #endif\n        }\n\n        #if !ORIGINAL_2_2_0_CODE\n        //// if interposes.count == 0 { return [] }\n        var rebindings = SwiftTrace.record(interposes: interposes, symbols: symbols)\n        return SwiftTrace.apply(rebindings: &rebindings,\n            onInjection: { (header, slide) in\n            var info = Dl_info()\n            // Need to apply previous interposes\n            // to the newly loaded dylib as well.\n            var previous = SwiftTrace.initialRebindings\n            var already = Set<UnsafeRawPointer>()\n            let interposed = NSObject.swiftTraceInterposed.bindMemory(to:\n                [UnsafeRawPointer : UnsafeRawPointer].self, capacity: 1)\n            for (replacee, _) in interposed.pointee {\n                if let replacement = SwiftTrace.interposed(replacee: replacee),\n                   already.insert(replacement).inserted,\n                   dladdr(replacee, &info) != 0, let symname = info.dli_sname {\n                    previous.append(rebinding(name: symname, replacement:\n                        UnsafeMutableRawPointer(mutating: replacement),\n                                              replaced: nil))\n                }\n            }\n            rebind_symbols_image(UnsafeMutableRawPointer(mutating: header),\n                                 slide, &previous, previous.count)\n        })\n        #else // ORIGINAL_2_2_0_CODE replaced by fishhook now\n        // Using array of new interpose structs\n        interposes.withUnsafeBufferPointer { interps in\n            var mostRecentlyLoaded = true\n\n            // Apply interposes to all images in the app bundle\n            // as well as the most recently loaded \"new\" dylib.\n            appBundleImages { image, header in\n                if mostRecentlyLoaded {\n                    // Need to apply all previous interposes\n                    // to the newly loaded dylib as well.\n                    var previous = Array<dyld_interpose_tuple>()\n                    for (replacee, replacement) in SwiftTrace.interposed {\n                        previous.append(dyld_interpose_tuple(\n                                replacement: replacement, replacee: replacee))\n                    }\n                    previous.withUnsafeBufferPointer {\n                        interps in\n                        dyld_dynamic_interpose(header,\n                                           interps.baseAddress!, interps.count)\n                    }\n                    mostRecentlyLoaded = false\n                }\n                // patch out symbols defined by new dylib.\n                dyld_dynamic_interpose(header,\n                                       interps.baseAddress!, interps.count)\n//                print(\"Patched \\(String(cString: image))\")\n            }\n        }\n        #endif\n        #endif\n    }\n\n    /// Interpose references to witness tables, meta data and perhps static variables\n    /// to those in main bundle to have them not re-initialise again on each injection.\n    static func reverseInterposeStaticsAddressors(_ tmpfile: String) {\n        var staticsAccessors = [rebinding]()\n        var already = Set<UnsafeRawPointer>()\n        var symbolSuffixes = [\"Wl\"] // Witness table accessors\n        if false && SwiftTrace.deviceInjection {\n            symbolSuffixes.append(\"Ma\") // meta data accessors\n        }\n        if SwiftTrace.preserveStatics {\n            symbolSuffixes.append(\"vau\") // static variable \"mutable addressors\"\n        }\n        for suffix in symbolSuffixes {\n            findHiddenSwiftSymbols(searchLastLoaded(), suffix, .any) {\n                accessor, symname, _, _ in\n                var original = dlsym(SwiftMeta.RTLD_MAIN_ONLY, symname)\n                if original == nil {\n                    original = findSwiftSymbol(searchBundleImages(), symname, .any)\n                    if original != nil && !already.contains(original!) {\n                        detail(\"Recovered top level variable with private scope \" +\n                               describeImagePointer(original!))\n                    }\n                }\n                guard original != nil, already.insert(original!).inserted else {\n                    return\n                }\n                detail(\"Reverse interposing \\(original!) <- \\(accessor) \" +\n                       describeImagePointer(original!))\n                staticsAccessors.append(rebinding(name: symname,\n                           replacement: original!, replaced: nil))\n            }\n        }\n        let injectedImage = _dyld_image_count()-1 // last injected\n        let interposed = SwiftTrace.apply(\n            rebindings: &staticsAccessors, count: staticsAccessors.count,\n            header: lastPseudoImage() ?? _dyld_get_image_header(injectedImage),\n            slide: lastPseudoImage() != nil ? 0 :\n                _dyld_get_image_vmaddr_slide(injectedImage))\n        for symname in interposed {\n            detail(\"Reverse interposed \"+describeImageSymbol(symname))\n        }\n        if interposed.count != staticsAccessors.count && injectionDetail {\n            let succeeded = Set(interposed)\n            for attemped in staticsAccessors.map({ $0.name }) {\n                if !succeeded.contains(attemped) {\n                    log(\"Reverse interposing \\(interposed.count)/\\(staticsAccessors.count) failed for \\(describeImageSymbol(attemped))\")\n                }\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/SwiftKeyPath.swift",
    "content": "//\n//  SwiftKeyPath.swift\n//\n//  Created by John Holdsworth on 20/03/2024.\n//  Copyright © 2024 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/SwiftKeyPath.swift#34 $\n//\n//  Key paths weren't made to be injected as their underlying types can change.\n//  This is particularly evident in code that uses \"The Composable Architecture\".\n//  This code maintains a cache of previously allocated key paths using a unique\n//  identifier of the calling site so they remain invariant over an injection.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\nprivate struct ViewBodyKeyPaths {\n    typealias KeyPathFunc = @convention(c) (UnsafeMutableRawPointer,\n                                            UnsafeRawPointer) -> UnsafeRawPointer\n\n    static let keyPathFuncName = \"swift_getKeyPath\"\n    static var save_getKeyPath: KeyPathFunc!\n\n    static var cache = [String: ViewBodyKeyPaths]()\n    static var lastInjectionNumber = SwiftEval().injectionNumber\n    static var hasInjected = false\n\n    var lastOffset = 0\n    var keyPathNumber = 0\n    var recycled = false\n    var keyPaths = [UnsafeRawPointer]()\n}\n\n@_cdecl(\"hookKeyPaths\")\npublic func hookKeyPaths(original: UnsafeMutableRawPointer,\n                         replacer: UnsafeMutableRawPointer) {\n    print(APP_PREFIX+\"ℹ️ Intercepting keypaths for when their types are injected.\" +\n        \" Set an env var INJECTION_NOKEYPATHS in your scheme to prevent this.\")\n    ViewBodyKeyPaths.save_getKeyPath = autoBitCast(original)\n    var keyPathRebinding = [rebinding(name: strdup(ViewBodyKeyPaths.keyPathFuncName),\n                                      replacement: replacer, replaced: nil)]\n    SwiftTrace.initialRebindings += keyPathRebinding\n    _ = SwiftTrace.apply(rebindings: &keyPathRebinding)\n}\n\n@_cdecl(\"injection_getKeyPath\")\npublic func injection_getKeyPath(pattern: UnsafeMutableRawPointer,\n                                 arguments: UnsafeRawPointer) -> UnsafeRawPointer {\n    if ViewBodyKeyPaths.lastInjectionNumber != SwiftEval.instance.injectionNumber {\n        ViewBodyKeyPaths.lastInjectionNumber = SwiftEval.instance.injectionNumber\n        for key in ViewBodyKeyPaths.cache.keys {\n            ViewBodyKeyPaths.cache[key]?.keyPathNumber = 0\n            ViewBodyKeyPaths.cache[key]?.recycled = false\n        }\n        ViewBodyKeyPaths.hasInjected = true\n    }\n    var info = Dl_info()\n    for caller in Thread.callStackReturnAddresses.dropFirst() {\n        guard let caller = caller.pointerValue,\n              dladdr(caller, &info) != 0, let symbol = info.dli_sname,\n              let callerDecl = SwiftMeta.demangle(symbol: symbol) else {\n            continue\n        }\n        if !callerDecl.hasSuffix(\".body.getter : some\") {\n            break\n        }\n        // identify caller site\n        var relevant: [String] = callerDecl[#\"(closure #\\d+ |in \\S+ : some)\"#]\n        if relevant.isEmpty {\n            relevant = [callerDecl]\n        }\n        let callerKey = relevant.joined() + \".keyPath#\"\n//        print(callerSym, ins)\n        var body = ViewBodyKeyPaths.cache[callerKey] ?? ViewBodyKeyPaths()\n        // reset keyPath counter ?\n        let offset = caller-info.dli_saddr\n        if offset <= body.lastOffset {\n            body.keyPathNumber = 0\n            body.recycled = false\n        }\n        body.lastOffset = offset\n//        print(\">>\", offset, body.keyPathNumber)\n        // extract cached keyPath or create\n        let keyPath: UnsafeRawPointer\n        if body.keyPathNumber < body.keyPaths.count && ViewBodyKeyPaths.hasInjected {\n            SwiftInjection.detail(\"Recycling \\(callerKey)\\(body.keyPathNumber)\")\n            keyPath = body.keyPaths[body.keyPathNumber]\n            body.recycled = true\n        } else {\n            keyPath = ViewBodyKeyPaths.save_getKeyPath(pattern, arguments)\n            if body.keyPaths.count == body.keyPathNumber {\n                body.keyPaths.append(keyPath)\n            }\n            if body.recycled {\n                SwiftInjection.log(\"\"\"\n                    ⚠️ New key path expression introduced over injection. \\\n                    This will likely fail and you'll have to restart your \\\n                    application.\n                    \"\"\")\n            }\n        }\n        body.keyPathNumber += 1\n        ViewBodyKeyPaths.cache[callerKey] = body\n        _ = Unmanaged<AnyKeyPath>.fromOpaque(keyPath).retain()\n        return keyPath\n    }\n    return ViewBodyKeyPaths.save_getKeyPath(pattern, arguments)\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/SwiftSweeper.swift",
    "content": "//\n//  SwiftSweeper.swift\n//\n//  Created by John Holdsworth on 15/04/2021.\n//\n//  The implementation of the memeory sweep to search for\n//  instance of classes that have been injected in order\n//  to be able to send them the @objc injected message.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/SwiftSweeper.swift#23 $\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n\n#if os(macOS)\nimport Cocoa\ntypealias OSApplication = NSApplication\n#elseif !os(watchOS)\nimport UIKit\ntypealias OSApplication = UIApplication\n#endif\n\n@objc public protocol SwiftInjected {\n    @objc optional func injected()\n}\n\nextension SwiftInjection {\n\n    static let debugSweep = getenv(INJECTION_SWEEP_DETAIL) != nil\n    static let sweepExclusions = { () -> NSRegularExpression? in\n        if let exclusions = getenv(INJECTION_SWEEP_EXCLUDE) {\n            let pattern = String(cString: exclusions)\n            do {\n                let filter = try NSRegularExpression(pattern: pattern, options: [])\n                print(APP_PREFIX+\"⚠️ Excluding types matching '\\(pattern)' from sweep\")\n                return filter\n            } catch {\n                print(APP_PREFIX+\"⚠️ Invalid sweep filter pattern \\(error): \\(pattern)\")\n            }\n        }\n        return nil\n    }()\n\n    static var sweepWarned = false\n\n    public class func performSweep(oldClasses: [AnyClass], _ tmpfile: String,\n                                 _ injectedGenerics: Set<String>) {\n        var injectedClasses = [AnyClass]()\n        for cls in oldClasses {\n            if class_getInstanceMethod(cls, injectedSEL) != nil {\n                injectedClasses.append(cls)\n                if !sweepWarned {\n                    log(\"\"\"\n                        As class \\(cls) has an @objc injected() \\\n                        method, \\(APP_NAME) will perform a \"sweep\" of live \\\n                        instances to determine which objects to message. \\\n                        If this fails, subscribe to the notification \\\n                        \"\\(INJECTION_BUNDLE_NOTIFICATION)\" instead.\n                        \\(APP_PREFIX)(note: notification may not arrive on the main thread)\n                        \"\"\")\n                    sweepWarned = true\n                }\n                let kvoName = \"NSKVONotifying_\" + NSStringFromClass(cls)\n                if let kvoCls = NSClassFromString(kvoName) {\n                    injectedClasses.append(kvoCls)\n                }\n            }\n        }\n\n        // implement -injected() method using sweep of objects in application\n        if !injectedClasses.isEmpty || !injectedGenerics.isEmpty {\n            log(\"Starting sweep \\(injectedClasses), \\(injectedGenerics)...\")\n            #if !os(watchOS)\n            let app = OSApplication.shared\n            let seeds: [Any] =  [app.delegate as Any] + app.windows\n            #else\n            let seeds = [Any]()\n            #endif\n            var patched = Set<UnsafeRawPointer>()\n            SwiftSweeper(instanceTask: {\n                (instance: AnyObject) in\n                if let instanceClass = object_getClass(instance),\n                   injectedClasses.contains(where: { $0 == instanceClass }) ||\n                    !injectedGenerics.isEmpty &&\n                    patchGenerics(oldClass: instanceClass, tmpfile: tmpfile,\n                        injectedGenerics: injectedGenerics, patched: &patched) {\n                    let proto = unsafeBitCast(instance, to: SwiftInjected.self)\n                    if SwiftEval.sharedInstance().vaccineEnabled {\n                        performVaccineInjection(instance)\n                        proto.injected?()\n                        return\n                    }\n\n                    proto.injected?()\n\n                    #if os(iOS) || os(tvOS)\n                    if let vc = instance as? UIViewController {\n                        flash(vc: vc)\n                    }\n                    #endif\n                }\n            }).sweepValue(seeds)\n        }\n    }\n\n}\n\nclass SwiftSweeper {\n\n    static var current: SwiftSweeper?\n\n    let instanceTask: (AnyObject) -> Void\n    var seen = [UnsafeRawPointer: Bool]()\n\n    init(instanceTask: @escaping (AnyObject) -> Void) {\n        self.instanceTask = instanceTask\n        SwiftSweeper.current = self\n    }\n\n    func sweepValue(_ value: Any, _ containsType: Bool = false) {\n        /// Skip values that cannot be cast into `AnyObject` because they end up being `nil`\n        /// Fixes a potential crash that the value is not accessible during injection.\n//        print(value)\n        guard !containsType && value as? AnyObject != nil else { return }\n\n        let mirror = Mirror(reflecting: value)\n        if var style = mirror.displayStyle {\n            if _typeName(mirror.subjectType).hasPrefix(\"Swift.ImplicitlyUnwrappedOptional<\") {\n                style = .optional\n            }\n            switch style {\n            case .set, .collection:\n                let containsType = _typeName(type(of: value)).contains(\".Type\")\n                if SwiftInjection.debugSweep {\n                    print(\"Sweeping collection:\", _typeName(type(of: value)))\n                }\n                for (_, child) in mirror.children {\n                    sweepValue(child, containsType)\n                }\n                return\n            case .dictionary:\n                for (_, child) in mirror.children {\n                    for (_, element) in Mirror(reflecting: child).children {\n                        sweepValue(element)\n                    }\n                }\n                return\n            case .class:\n                sweepInstance(value as AnyObject)\n                return\n            case .optional, .enum:\n                if let evals = mirror.children.first?.value {\n                    sweepValue(evals)\n                }\n            case .tuple, .struct:\n                sweepMembers(value)\n            @unknown default:\n                break\n            }\n        }\n    }\n\n    func sweepInstance(_ instance: AnyObject) {\n        let reference = unsafeBitCast(instance, to: UnsafeRawPointer.self)\n        if seen[reference] == nil {\n            seen[reference] = true\n            if let filter = SwiftInjection.sweepExclusions {\n                let typeName = _typeName(type(of: instance))\n                if filter.firstMatch(in: typeName,\n                    range: NSMakeRange(0, typeName.utf16.count)) != nil {\n                    return\n                }\n            }\n\n            if SwiftInjection.debugSweep {\n                print(\"Sweeping instance \\(reference) of class \\(type(of: instance))\")\n            }\n\n            sweepMembers(instance)\n            instance.legacySwiftSweep?()\n\n            instanceTask(instance)\n        }\n    }\n\n    func sweepMembers(_ instance: Any) {\n        var mirror: Mirror? = Mirror(reflecting: instance)\n        while mirror != nil {\n            for (name, value) in mirror!.children\n                where name?.hasSuffix(\"Type\") != true {\n                sweepValue(value)\n            }\n            mirror = mirror!.superclassMirror\n        }\n    }\n}\n\nextension NSObject {\n    @objc func legacySwiftSweep() {\n        var icnt: UInt32 = 0, cls: AnyClass? = object_getClass(self)\n        while cls != nil && cls != NSObject.self && cls != NSURL.self {\n            let className = NSStringFromClass(cls!)\n            if className.hasPrefix(\"_\") || className.hasPrefix(\"WK\") ||\n                className.hasPrefix(\"NS\") && className != \"NSWindow\" {\n                return\n            }\n            if let ivars = class_copyIvarList(cls, &icnt) {\n                let object = UInt8(ascii: \"@\")\n                for i in 0 ..< Int(icnt) {\n                    if /*let name = ivar_getName(ivars[i])\n                        .flatMap({ String(cString: $0)}),\n                       sweepExclusions?.firstMatch(in: name,\n                           range: NSMakeRange(0, name.utf16.count)) == nil,*/\n                       let type = ivar_getTypeEncoding(ivars[i]), type[0] == object {\n                        (unsafeBitCast(self, to: UnsafePointer<Int8>.self) + ivar_getOffset(ivars[i]))\n                            .withMemoryRebound(to: AnyObject?.self, capacity: 1) {\n//                                print(\"\\($0.pointee) \\(self) \\(name):  \\(String(cString: type))\")\n                                if let obj = $0.pointee {\n                                    SwiftSweeper.current?.sweepInstance(obj)\n                                }\n                        }\n                    }\n                }\n                free(ivars)\n            }\n            cls = class_getSuperclass(cls)\n        }\n    }\n}\n\nextension NSSet {\n    @objc override func legacySwiftSweep() {\n        self.forEach { SwiftSweeper.current?.sweepInstance($0 as AnyObject) }\n    }\n}\n\nextension NSArray {\n    @objc override func legacySwiftSweep() {\n        self.forEach { SwiftSweeper.current?.sweepInstance($0 as AnyObject) }\n    }\n}\n\nextension NSDictionary {\n    @objc override func legacySwiftSweep() {\n        self.allValues.forEach { SwiftSweeper.current?.sweepInstance($0 as AnyObject) }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/UnhidingEval.swift",
    "content": "//\n//  UnhidingEval.swift\n//\n//  Created by John Holdsworth on 13/04/2021.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloading/UnhidingEval.swift#27 $\n//\n//  Retro-fit Unhide into InjectionIII\n//\n//  Unhiding is a work-around for swift giving \"hidden\" visibility\n//  to default argument generators which are called when code uses\n//  a default argument. \"Hidden\" visibility is somewhere between a\n//  public and private declaration where the symbol doesn't become\n//  part of the Swift ABI but is nevertheless required at call sites.\n//  This causes problems for injection as \"hidden\" symbols are not\n//  available outside the framework or executable that defines them.\n//  So, a dynamically loading version of a source file that uses a\n//  default argument cannot load due to not seeing the symbol.\n//\n//  This file calls a piece of C++ in Unhide.mm which scans all the object\n//  files of a project looking for symbols for default argument generators\n//  that are hidden and makes them public by clearing the N_PEXT flag on\n//  the symbol type. Ideally this would happen between compiling and linking\n//  But as it is not possible to add a build phase between compiling and\n//  linking you have to build again for the object file to be linked into\n//  the app executable or framework. This isn't ideal but is about as\n//  good as it gets, resolving the injection of files that use default\n//  arguments with the minimum disruption to the build process. This\n//  file inserts this process when injection is used to keep the files\n//  declaring the defaut argument patched sometimes giving an error that\n//  asks the user to run the app again and retry.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\nimport Foundation\n#if SWIFT_PACKAGE\nimport SwiftRegex\n#endif\n\n@objc\npublic class UnhidingEval: SwiftEval {\n\n    @objc public override class func sharedInstance() -> SwiftEval {\n        SwiftEval.instance = UnhidingEval()\n        return SwiftEval.instance\n    }\n\n    static let unhideQueue = DispatchQueue(label: \"unhide\")\n\n    static var lastProcessed = [URL: time_t]()\n\n    var unhidden = false\n    var buildDir: URL?\n\n    public override func determineEnvironment(classNameOrFile: String) throws -> (URL, URL) {\n        let (project, logs) =\n            try super.determineEnvironment(classNameOrFile: classNameOrFile)\n        buildDir = logs.deletingLastPathComponent()\n            .deletingLastPathComponent().appendingPathComponent(\"Build\")\n        if legacyUnhide {\n            startUnhide()\n        }\n        return (project, logs)\n    }\n\n    override func startUnhide() {\n        if !unhidden, let buildDir = buildDir {\n            unhidden = true\n            Self.unhideQueue.async {\n                if let enumerator = FileManager.default\n                    .enumerator(atPath: buildDir.path),\n                let log = fopen(\"/tmp/unhide.log\", \"w\") {\n                    // linkFileLists contain the list of object files.\n                    var linkFileLists = [String](), frameworks = [String]()\n                    for path in enumerator.compactMap({ $0 as? String }) {\n                        if path.hasSuffix(\".LinkFileList\") {\n                            linkFileLists.append(path)\n                        } else if path.hasSuffix(\".framework\") {\n                            frameworks.append(path)\n                        }\n                    }\n                    // linkFileLists sorted to process packages\n                    // first due to Edge case in Fruta example.\n                    let since = Self.lastProcessed[buildDir] ?? 0\n                    for path in linkFileLists.sorted(by: {\n                        ($0.hasSuffix(\".o.LinkFileList\") ? 0 : 1) <\n                        ($1.hasSuffix(\".o.LinkFileList\") ? 0 : 1) }) {\n                        let fileURL = buildDir\n                            .appendingPathComponent(path)\n                        let exported = unhide_symbols(fileURL\n                            .deletingPathExtension().deletingPathExtension()\n                            .lastPathComponent, fileURL.path, log, since)\n                        if exported != 0 {\n                            let s = exported == 1 ? \"\" : \"s\"\n                            print(\"\\(APP_PREFIX)Exported \\(exported) default argument\\(s) in \\(fileURL.lastPathComponent)\")\n                        }\n                    }\n\n                    #if false // never implemented\n                    for framework in frameworks {\n                        let fileURL = buildDir\n                            .appendingPathComponent(framework)\n                        let frameworkName = fileURL\n                            .deletingPathExtension().lastPathComponent\n                        let exported = unhide_framework(fileURL\n                            .appendingPathComponent(frameworkName).path, log)\n                        if exported != 0 {\n                            let s = exported == 1 ? \"\" : \"s\"\n                            print(\"\\(APP_PREFIX)Exported \\(exported) symbol\\(s) in framework \\(frameworkName)\")\n                        }\n                    }\n                    #endif\n\n                    Self.lastProcessed[buildDir] = time(nil)\n                    unhide_reset()\n                    fclose(log)\n                }\n            }\n        }\n    }\n\n    // This was required for Xcode13's new \"optimisation\" to compile\n    // more than one primary file in a single compiler invocation.\n    override func xcode13Fix(sourceFile: String,\n                             compileCommand: inout String) -> String {\n        let sourceName = URL(fileURLWithPath: sourceFile)\n            .deletingPathExtension().lastPathComponent.escaping().lowercased()\n        let hasFileList = compileCommand.contains(\" -filelist \")\n        var nPrimaries = 0\n        // ensure there is only ever one -primary-file argument and object file\n        // avoids shuffling of object files due to how the compiler is coded\n        compileCommand[#\" -primary-file (\\#(Self.filePathRegex+Self.fileNameRegex))\"#] = {\n            (groups: [String], stop) -> String in\n//            debug(\"PF: \\(sourceName) \\(groups)\")\n            nPrimaries += 1\n            return groups[2].lowercased() == sourceName ||\n                   groups[3].lowercased() == sourceName ?\n                groups[0] : hasFileList ? \"\" : \" \"+groups[1]\n        }\n//        // Xcode 13 can have multiple primary files but implements default\n//        // arguments in a way that no longer requires they be \"unhidden\".\n//        if nPrimaries < 2 {\n//            startUnhide()\n//        }\n        // The number of these options must match the number of -primary-file arguments\n        // which has just been changed to only ever be one so, strip them out\n        let toRemove = #\" -(serialize-diagnostics|emit-(module(-doc|-source-info)?|(reference-)?dependencies|const-values)|index-unit-output)-path \"#\n        compileCommand = compileCommand\n            .replacingOccurrences(of: toRemove + Self.argumentRegex,\n                                  with: \"\", options: .regularExpression)\n        debug(\"Uniqued command:\", compileCommand)\n        // Replace path(s) of all object files to a single one\n        return super.xcode13Fix(sourceFile: sourceFile,\n                                compileCommand: &compileCommand)\n    }\n\n    /// Per-object file version of unhiding on injection to export some symbols\n    /// - Parameters:\n    ///   - executable: Path to app executable to extract module name\n    ///   - objcClassRefs: Array to accumulate class referrences\n    ///   - descriptorRefs: Array to accumulate \"l.got\" references to \"fixup\"\n    override func createUnhider(executable: String, _ objcClassRefs: NSMutableArray,\n                                _ descriptorRefs: NSMutableArray) {\n        let appModule = URL(fileURLWithPath: executable)\n            .lastPathComponent.replacingOccurrences(of: \" \", with: \"_\")\n        let appPrefix = \"$s\\(appModule.count)\\(appModule)\"\n        objectUnhider = { object_file in\n            let logfile = \"/tmp/unhide_object.log\"\n            if let log = fopen(logfile, \"w\") {\n                setbuf(log, nil)\n                objcClassRefs.removeAllObjects()\n                descriptorRefs.removeAllObjects()\n                unhide_object(object_file, appPrefix, log,\n                              objcClassRefs, descriptorRefs)\n//                self.log(\"Unhidden: \\(object_file) -- \\(appPrefix) -- \\(self.objcClassRefs)\")\n            } else {\n//                self.log(\"Could not log to \\(logfile)\")\n            }\n        }\n    }\n\n    // Non-essential functionality moved here...\n    @objc public func rebuild(storyboard: String) throws {\n        let (_, logsDir) = try determineEnvironment(classNameOrFile: storyboard)\n\n        injectionNumber += 1\n\n        // messy but fast\n        guard shell(command: \"\"\"\n            # search through build logs, most recent first\n            cd \"\\(logsDir.path.escaping(\"$\"))\" &&\n            for log in `ls -t *.xcactivitylog`; do\n                #echo \"Scanning $log\"\n                /usr/bin/env perl <(cat <<'PERL'\n                    use English;\n                    use strict;\n\n                    # line separator in Xcode logs\n                    $INPUT_RECORD_SEPARATOR = \"\\\\r\";\n\n                    # format is gzip\n                    open GUNZIP, \"/usr/bin/gunzip <\\\\\"$ARGV[0]\\\\\" 2>/dev/null |\" or die;\n\n                    # grep the log until to find codesigning for product path\n                    my $realPath;\n                    while (defined (my $line = <GUNZIP>)) {\n                        if ($line =~ /^\\\\s*cd /) {\n                            $realPath = $line;\n                        }\n                        elsif (my ($product) = $line =~ m@/usr/bin/ibtool.*? --link (([^\\\\ ]+\\\\\\\\ )*\\\\S+\\\\.app)@o) {\n                            print $product;\n                            exit 0;\n                        }\n                    }\n\n                    # class/file not found\n                    exit 1;\n            PERL\n                ) \"$log\" >\"\\(tmpfile).sh\" && exit 0\n            done\n            exit 1;\n            \"\"\") else {\n            throw scriptError(\"Locating storyboard compile\")\n        }\n\n        guard let resources = try? String(contentsOfFile: \"\\(tmpfile).sh\")\n            .trimmingCharacters(in: .whitespaces) else {\n            throw scriptError(\"Locating product\")\n        }\n\n        guard shell(command: \"\"\"\n            (cd \"\\(resources.unescape().escaping(\"$\"))\" && for i in 1 2 3 4 5; \\\n            do if (find . -name '*.nib' -a -newer \"\\(storyboard)\" | \\\n            grep .nib >/dev/null); then break; fi; sleep 1; done; \\\n            while (ps auxww | grep -v grep | grep \"/ibtool \" >/dev/null); do sleep 1; done; \\\n            for i in `find . -name '*.nib'`; do cp -rf \"$i\" \"\\(\n                        Bundle.main.bundlePath)/$i\"; done >\"\\(logfile)\" 2>&1)\n            \"\"\") else {\n                throw scriptError(\"Re-compilation\")\n        }\n\n        _ = evalError(\"Copied \\(storyboard)\")\n    }\n\n    // Assorted bazel code moved out of SwiftEval.swift\n    override func bazelLink(in projectRoot: String, since sourceFile: String,\n                   compileCommand: String) throws -> String {\n        guard var objects = bazelFiles(under: projectRoot, where: \"\"\"\n            -newer \"\\(sourceFile)\" -a -name '*.o' | \\\n            egrep '(_swift_incremental|_objs)/' | grep -v /external/\n            \"\"\") else {\n            throw evalError(\"Finding Objects failed. Did you actually make a change to \\(sourceFile) and does it compile? InjectionIII does not support whole module optimization. (check logfile: \\(logfile))\")\n        }\n\n        debug(bazelLight, projectRoot, objects)\n        // precendence to incrementally compiled\n        let incremental = objects.filter({ $0.contains(\"_swift_incremental\") })\n        if incremental.count > 0 {\n            objects = incremental\n        }\n\n        #if true\n        // With WMO, which modifies all object files\n        // we need a way to filter them down to that\n        // of the source file and its related module\n        // library to provide shared \"hidden\" symbols.\n        // We use the related Swift \"output_file_map\"\n        // for the module name to include its library.\n        if objects.count > 1 {\n            let objectSet = Set(objects)\n            if let maps  = bazelFiles(under: projectRoot,\n                                      where: \"-name '*output_file_map*.json'\") {\n                let relativePath = sourceFile .replacingOccurrences(\n                    of: projectRoot+\"/\", with: \"\")\n                for map in maps {\n                    if let data = try? Data(contentsOf: URL(\n                        fileURLWithPath: projectRoot)\n                        .appendingPathComponent(map)),\n                       let json = try? JSONSerialization.jsonObject(\n                        with: data, options: []) as? [String: Any],\n                       let info = json[relativePath] as? [String: String] {\n                        if let object = info[\"object\"],\n                           objectSet.contains(object) {\n                            objects = [object]\n                            if let module: String =\n                                map[#\"(\\w+)\\.output_file_map\"#] {\n                                for lib in bazelFiles(under: projectRoot,\n                                    where: \"-name 'lib\\(module).a'\") ?? [] {\n                                    moduleLibraries.insert(lib)\n                                }\n                            }\n                            break\n                        }\n                    }\n                }\n            } else {\n                _ = evalError(\"Error reading maps\")\n            }\n        }\n        #endif\n\n        objects += moduleLibraries\n        debug(objects)\n\n        try link(dylib: \"\\(tmpfile).dylib\", compileCommand: compileCommand,\n                 contents: objects.map({ \"\\\"\\($0)\\\"\"}).joined(separator: \" \"),\n                 cd: projectRoot)\n        return tmpfile\n    }\n\n    func bazelFiles(under projectRoot: String, where clause:  String,\n                    ext: String = \"files\") -> [String]? {\n        let list = \"\\(tmpfile).\\(ext)\"\n        if shell(command: \"\"\"\n            cd \"\\(projectRoot)\" && \\\n            find bazel-out/* \\(clause) >\"\\(list)\" 2>>\\\"\\(logfile)\\\"\n            \"\"\"), let files = (try? String(contentsOfFile: list))?\n            .components(separatedBy: \"\\n\").dropLast() {\n            return Array(files)\n        }\n        return nil\n    }\n\n    override func bazelLight(projectRoot: String, recompile sourceFile: String) throws -> String? {\n        let relativePath = sourceFile.replacingOccurrences(of:\n            projectRoot+\"/\", with: \"\")\n        let bazelRulesSwift = projectRoot +\n            \"/bazel-out/../external/build_bazel_rules_swift\"\n        let paramsScanner = tmpDir + \"/bazel.pl\"\n        debug(projectRoot, relativePath, bazelRulesSwift, paramsScanner)\n\n        if !sourceFile.hasSuffix(\".swift\") {\n            throw evalError(\"Only Swift sources can be standalone injected with bazel\")\n        }\n\n        try #\"\"\"\n            use JSON::PP;\n            use English;\n            use strict;\n\n            my ($params, $relative) = @ARGV;\n            my $args = join('', (IO::File->new( \"< $params\" )\n                or die \"Could not open response '$params'\")->getlines());\n            my ($filemap) = $args =~ /\"-output-file-map\"\\n\"([^\"]+)\"/;\n            my $file_handle = IO::File->new( \"< $filemap\" )\n                or die \"Could not open filemap '$filemap'\";\n            my $json_text = join'', $file_handle->getlines();\n            my $json_map = decode_json( $json_text, { utf8  => 1 } );\n\n            if (my $info = $json_map->{$relative}) {\n                $args =~ s/\"-(emit-(module|objc-header)-path\"\\n\"[^\"]+)\"\\n//g;\n                my $paramscopy = \"$params.copy\";\n                my $paramsfile = IO::File->new(\"> $paramscopy\");\n                binmode $paramsfile, ':utf8';\n                $paramsfile->print($args);\n                $paramsfile->close();\n                print \"$paramscopy\\n$info->{object}\\n\";\n                exit 0;\n            }\n            # source file not found\n            exit 1;\n            \"\"\"#.write(toFile: paramsScanner,\n                       atomically: false, encoding: .utf8)\n\n        let errfile = \"\\(tmpfile).err\"\n        guard shell(command: \"\"\"\n            # search through bazel args, most recent first\n            cd \"\\(bazelRulesSwift)\" 2>\"\\(errfile)\" &&\n            grep module_name_ tools/worker/swift_runner.h >/dev/null 2>>\"\\(errfile)\" ||\n            (git apply -v <<'BAZEL_PATCH' 2>>\"\\(errfile)\" && echo \"⚠️ bazel patched, restart app\" >>\"\\(errfile)\" && exit 1) &&\n            diff --git a/tools/worker/swift_runner.cc b/tools/worker/swift_runner.cc\n            index 535dad0..19e1a6d 100644\n            --- a/tools/worker/swift_runner.cc\n            +++ b/tools/worker/swift_runner.cc\n            @@ -369,6 +369,11 @@ std::vector<std::string> SwiftRunner::ParseArguments(Iterator itr) {\n                     arg = *it;\n                     output_file_map_path_ = arg;\n                     out_args.push_back(arg);\n            +      } else if (arg == \"-module-name\") {\n            +        ++it;\n            +        arg = *it;\n            +        module_name_ = arg;\n            +        out_args.push_back(arg);\n                   } else if (arg == \"-index-store-path\") {\n                     ++it;\n                     arg = *it;\n            @@ -410,12 +415,28 @@ std::vector<std::string> SwiftRunner::ProcessArguments(\n                 ++it;\n               }\n\n            +  auto copy = \"/tmp/bazel_\"+module_name_+\".params\";\n            +  unlink(copy.c_str());\n               if (force_response_file_) {\n                 // Write the processed args to the response file, and push the path to that\n                 // file (preceded by '@') onto the arg list being returned.\n                 auto new_file = WriteResponseFile(response_file_args);\n                 new_args.push_back(\"@\" + new_file->GetPath());\n            +\n            +    // patch to retain swiftc arguments file\n            +    link(new_file->GetPath().c_str(), copy.c_str());\n                 temp_files_.push_back(std::move(new_file));\n            +  } else if (FILE *fp = fopen(\"/tmp/forced_params.txt\", \"w+\")) {\n            +    // alternate patch to capture arguments file\n            +    for (auto &a : args_destination) {\n            +      const char *carg = a.c_str();\n            +      fprintf(fp, \"%s\\\\n\", carg);\n            +      if (carg[0] != '@')\n            +          continue;\n            +      link(carg+1, copy.c_str());\n            +      fprintf(fp, \"Linked %s to %s\\\\n\", copy.c_str(), carg+1);\n            +    }\n            +    fclose(fp);\n               }\n\n               return new_args;\n            diff --git a/tools/worker/swift_runner.h b/tools/worker/swift_runner.h\n            index 952c593..35cf055 100644\n            --- a/tools/worker/swift_runner.h\n            +++ b/tools/worker/swift_runner.h\n            @@ -153,6 +153,9 @@ class SwiftRunner {\n               // The index store path argument passed to the runner\n               std::string index_store_path_;\n\n            +  // Swift modue name from -module-name\n            +  std::string module_name_ = \"Unknown\";\n            +\n               // The path of the global index store  when using\n               // swift.use_global_index_store. When set, this is passed to `swiftc` as the\n               // `-index-store-path`. After running `swiftc` `index-import` copies relevant\n            BAZEL_PATCH\n\n            cd \"\\(projectRoot)\" 2>>\"\\(errfile)\" &&\n            for params in `ls -t /tmp/bazel_*.params 2>>\"\\(errfile)\"`; do\n                #echo \"Scanning $params\"\n                /usr/bin/env perl \"\\(paramsScanner)\" \"$params\" \"\\(relativePath)\" \\\n                >\"\\(tmpfile).sh\" 2>>\"\\(errfile)\" && exit 0\n            done\n            exit 1;\n            \"\"\"), let returned = (try? String(contentsOfFile: \"\\(tmpfile).sh\"))?\n                                        .components(separatedBy: \"\\n\") else {\n            if let log = try? String(contentsOfFile: errfile), log != \"\" {\n                throw evalError(log.contains(\"ls: /tmp/bazel_*.params\") ? \"\"\"\n                    \\(log)Response files not available (see: \\(cmdfile))\n                    Edit and save a swift source file and restart app.\n                    \"\"\" : \"\"\"\n                    Locating response file failed (see: \\(cmdfile))\n                    \\(log)\n                    \"\"\")\n            }\n            return nil\n        }\n\n        let params = returned[0]\n        _ = evalError(\"Compiling using parameters from \\(params)\")\n\n        guard shell(command: \"\"\"\n                cd \"\\(projectRoot)\" && \\\n                chmod +w `find bazel-out/* -name '*.o'`; \\\n                xcrun swiftc @\\(params) >\\\"\\(logfile)\\\" 2>&1\n                \"\"\") || shell(command: \"\"\"\n                cd `readlink \"\\(projectRoot)/bazel-out\"`/.. && \\\n                chmod +w `find bazel-out/* -name '*.o'`; \\\n                xcrun swiftc @\\(params) >>\\\"\\(logfile)\\\" 2>&1\n                \"\"\"),\n              let compileCommand = try? String(contentsOfFile: params) else {\n            throw scriptError(\"Recompiling\")\n        }\n\n        return try bazelLink(in: projectRoot, since: sourceFile,\n                             compileCommand: compileCommand)\n    }\n\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloading/Vaccine.swift",
    "content": "#if (DEBUG || !SWIFT_PACKAGE) && !os(watchOS)\n#if os(macOS)\nimport Cocoa\ntypealias View = NSView\ntypealias ViewController = NSViewController\ntypealias ScrollView = NSScrollView\n#else\nimport UIKit\ntypealias View = UIView\ntypealias ViewController = UIViewController\ntypealias ScrollView = UIScrollView\n\nfileprivate extension ViewController {\n    func viewWillAppear() { self.viewWillAppear(false) }\n    func viewDidAppear() { self.viewDidAppear(false) }\n}\n#endif\n\nextension View {\n    func subviewsRecursive() -> [View] {\n        return subviews + subviews.flatMap { $0.subviewsRecursive() }\n    }\n}\n\nclass Vaccine {\n    func performInjection(on object: AnyObject) {\n        switch object {\n        case let viewController as ViewController:\n            let snapshotView: View? = createSnapshotViewIfNeeded(for: viewController)\n            if viewController.nibName == nil {\n                CATransaction.begin()\n                CATransaction.setAnimationDuration(1.0)\n                CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))\n                defer { CATransaction.commit() }\n            }\n\n            let oldScrollViews = indexScrollViews(on: viewController)\n\n            #if os(macOS)\n            reload(viewController.parent ?? viewController)\n            #else\n            // Opt-out from performing Vaccine reloads on parent\n            // if the parent is either a navigation controller or\n            // a tab bar controller.\n            if let parentViewController = viewController.parent {\n              if parentViewController is UINavigationController {\n                reload(viewController)\n              } else if parentViewController is UITabBarController {\n                reload(viewController)\n              } else {\n                reload(parentViewController)\n              }\n            } else {\n              reload(viewController)\n            }\n            #endif\n\n            syncOldScrollViews(oldScrollViews, with: indexScrollViews(on: viewController))\n            cleanSnapshotViewIfNeeded(snapshotView, viewController: viewController)\n        case let view as View:\n            reload(view)\n        default:\n            break\n        }\n    }\n\n    private func reload(_ viewController: ViewController) {\n        viewController.view.subviews.forEach { $0.removeFromSuperview() }\n        clean(view: viewController.view)\n        viewController.loadView()\n        viewController.viewDidLoad()\n        viewController.viewWillAppear()\n        viewController.viewDidAppear()\n        refreshSubviews(on: viewController.view)\n    }\n\n    private func reload(_ view: View) {\n        let selector = _Selector(\"loadView\")\n        guard view.responds(to: selector) == true else { return }\n\n        #if os(macOS)\n        view.animator().perform(selector)\n        #else\n        UIView.animate(withDuration: 0.3, delay: 0.0, options: [.allowAnimatedContent,\n                                                                .beginFromCurrentState,\n                                                                .layoutSubviews], animations: {\n                                                                    view.perform(selector)\n        }, completion: nil)\n        #endif\n    }\n\n    private func createSnapshotViewIfNeeded(for viewController: ViewController) -> View? {\n        if let snapshotView = createSnapshot(from: viewController.view), viewController.nibName == nil {\n            #if os(macOS)\n            viewController.view.window?.contentView?.addSubview(snapshotView)\n            #else\n            let maskView = UIView()\n            maskView.frame.size = snapshotView.frame.size\n            maskView.frame.origin.y = viewController.navigationController?.navigationBar.frame.maxY ?? 0\n            maskView.backgroundColor = .white\n            snapshotView.mask = maskView\n            viewController.view.window?.addSubview(snapshotView)\n            #endif\n\n            return snapshotView\n\n        }\n        return nil\n    }\n\n    private func cleanSnapshotViewIfNeeded(_ snapshotView: View?, viewController: ViewController) {\n        if let snapshotView = snapshotView, viewController.nibName == nil {\n            #if os(macOS)\n            NSAnimationContext.runAnimationGroup({ (context) in\n                context.allowsImplicitAnimation = true\n                context.duration = 0.25\n                snapshotView.animator().alphaValue = 0.0\n            }, completionHandler: {\n                snapshotView.removeFromSuperview()\n            })\n            #else\n            UIView.animate(withDuration: 0.25,\n                           delay: 0.0,\n                           options: [.allowAnimatedContent,\n                                     .beginFromCurrentState,\n                                     .layoutSubviews],\n                           animations: {\n                            snapshotView.alpha = 0.0\n            }) { _ in\n                snapshotView.removeFromSuperview()\n            }\n            #endif\n        }\n    }\n\n    private func clean(view: View) {\n        view.subviews.forEach { $0.removeFromSuperview() }\n\n        #if os(macOS)\n        if let sublayers = view.layer?.sublayers {\n            sublayers.forEach { $0.removeFromSuperlayer() }\n        }\n        #else\n        if let sublayers = view.layer.sublayers {\n            sublayers.forEach { $0.removeFromSuperlayer() }\n        }\n        #endif\n    }\n\n    private func refreshSubviews(on view: View) {\n        #if os(macOS)\n        view.subviewsRecursive().forEach { view in\n            (view as? NSTableView)?.reloadData()\n            (view as? NSCollectionView)?.reloadData()\n            view.needsLayout = true\n            view.layout()\n            view.needsDisplay = true\n            view.display()\n        }\n        #else\n        view.subviewsRecursive().forEach { view in\n            (view as? UITableView)?.reloadData()\n            (view as? UICollectionView)?.reloadData()\n            view.setNeedsLayout()\n            view.layoutIfNeeded()\n            view.setNeedsDisplay()\n        }\n        #endif\n    }\n\n    private func indexScrollViews(on viewController: ViewController) -> [ScrollView] {\n        var scrollViews = [ScrollView]()\n\n        for case let scrollView as ScrollView in viewController.view.subviews {\n            scrollViews.append(scrollView)\n        }\n\n        if let parentViewController = viewController.parent {\n            for case let scrollView as ScrollView in parentViewController.view.subviews {\n                scrollViews.append(scrollView)\n            }\n        }\n\n        for childViewController in viewController.children {\n            for case let scrollView as ScrollView in childViewController.view.subviews {\n                scrollViews.append(scrollView)\n            }\n        }\n\n        return scrollViews\n    }\n\n    private func syncOldScrollViews(_ oldScrollViews: [ScrollView], with newScrollViews: [ScrollView]) {\n        for (offset, scrollView) in newScrollViews.enumerated() {\n            if offset < oldScrollViews.count {\n                let oldScrollView = oldScrollViews[offset]\n                if type(of: scrollView) == type(of: oldScrollView) {\n                    #if os(macOS)\n                    scrollView.contentView.scroll(to: oldScrollView.documentVisibleRect.origin)\n                    #else\n                    scrollView.contentOffset = oldScrollView.contentOffset\n                    #endif\n                }\n            }\n        }\n    }\n\n    private func createSnapshot(from view: View) -> View? {\n        #if os(macOS)\n        let snapshot = NSImageView()\n        snapshot.image = view.snapshot\n        snapshot.frame.size = view.frame.size\n        return snapshot\n        #else\n        return view.snapshotView(afterScreenUpdates: true)\n        #endif\n    }\n\n    private func _Selector(_ string: String) -> Selector {\n        return Selector(string)\n    }\n}\n\n#if os(macOS)\nfileprivate extension NSView {\n    var snapshot: NSImage {\n        guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return NSImage() }\n        cacheDisplay(in: bounds, to: bitmapRep)\n        let image = NSImage()\n        image.addRepresentation(bitmapRep)\n        bitmapRep.size = bounds.size.doubleScale()\n        return image\n    }\n}\nfileprivate extension CGSize {\n    func doubleScale() -> CGSize {\n        return CGSize(width: width * 2, height: height * 2)\n    }\n}\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloadingGuts/ClientBoot.mm",
    "content": "//\n//  ClientBoot.mm\n//  InjectionIII\n//\n//  Created by John Holdsworth on 02/24/2021.\n//  Copyright © 2021 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloadingGuts/ClientBoot.mm#131 $\n//\n//  Initiate connection to server side of InjectionIII/HotReloading.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#import \"InjectionClient.h\"\n#import <XCTest/XCTest.h>\n#import <objc/runtime.h>\n#import \"SimpleSocket.h\"\n#import <dlfcn.h>\n\n#ifndef INJECTION_III_APP\nNSString *INJECTION_KEY = @__FILE__;\n#endif\n\n#if defined(DEBUG) || defined(INJECTION_III_APP)\nstatic SimpleSocket *injectionClient;\nNSString *injectionHost = @\"127.0.0.1\";\nstatic dispatch_once_t onlyOneClient;\n\n@implementation SimpleSocket(Connect)\n\n+ (void)backgroundConnect:(const char *)host {\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{\n        if (SimpleSocket *client = [[self class] connectTo:[NSString\n                stringWithFormat:@\"%s%@\", host, @INJECTION_ADDRESS]])\n            dispatch_once(&onlyOneClient, ^{\n                injectionHost = [NSString stringWithUTF8String:host];\n                [injectionClient = client run];\n            });\n    });\n}\n\n@end\n\n@interface BundleInjection: NSObject\n@end\n@implementation BundleInjection\n\nextern \"C\" {\n    extern void hookKeyPaths(void *original, void *replacement);\n    extern const void *swift_getKeyPath(void *, const void *);\n    extern const void *injection_getKeyPath(void *, const void *);\n    extern void injection_hookGenerics(void *original, void *replacement);\n    extern Class swift_allocateGenericClassMetadata(void *, void *, void *);\n    extern Class injection_allocateGenericClassMetadata(void *, void *, void *);\n}\n\n+ (void)load {\n    // If the custom TEST_HOST used to load XCTestBundle, and this custom TEST_HOST is a normal iOS/macOS/tvOS app,\n    // then XCTestCase can instantiate NSWindow / UIWindow, make it key and visible, pause and wait for\n    // standard expectation – XCTestExpectation. This untypical flow used for per-screen UI development without need\n    // to launch full app. To support this flow we are allowing injection by respecting dedicated environment variable.\n    bool shouldUseInTests = getenv(INJECTION_USEINTESTS) != nullptr;\n\n    // See: https://forums.developer.apple.com/forums/thread/761439\n    bool isPreviewsDetected = getenv(\"XCODE_RUNNING_FOR_PREVIEWS\") != nullptr;\n    // See: https://github.com/pointfreeco/swift-issue-reporting/blob/main/Sources/IssueReporting/IsTesting.swift#L29\n    bool isTestsDetected = getenv(\"XCTestBundlePath\") ||\n                           getenv(\"XCTestSessionIdentifier\") ||\n                           getenv(\"XCTestConfigurationFilePath\");\n    if (isPreviewsDetected || (isTestsDetected && !shouldUseInTests))\n        return; // inhibit in previews or when running tests, unless explicitly enabled in tests.\n    #if !(SWIFT_PACKAGE && TARGET_OS_OSX)\n    if (!getenv(INJECTION_NOKEYPATHS) && (getenv(INJECTION_KEYPATHS)\n        #if !SWIFT_PACKAGE\n        || dlsym(RTLD_DEFAULT, \"$s22ComposableArchitecture6LoggerCN\")\n        #endif\n        ))\n        hookKeyPaths((void *)swift_getKeyPath, (void *)injection_getKeyPath);\n    #endif\n    #if !SWIFT_PACKAGE\n    if (!getenv(INJECTION_NOGENERICS) && !getenv(INJECTION_OF_GENERICS))\n        injection_hookGenerics((void *)swift_allocateGenericClassMetadata,\n                               (void *)injection_allocateGenericClassMetadata);\n    #else\n    printf(APP_PREFIX\"⚠️ Define env var \" INJECTION_OF_GENERICS\n           \" in your scheme to inject generic classes.\\n\");\n    #endif\n    if (Class clientClass = objc_getClass(\"InjectionClient\"))\n        [self performSelectorInBackground:@selector(tryConnect:)\n                               withObject:clientClass];\n}\n\n+ (void)tryConnect:(Class)clientClass {\n    NSString *socketAddr = @INJECTION_ADDRESS;\n    __unused const char *buildPhase = APP_PREFIX\"You'll need to be running \"\n        \"a recent copy of the InjectionIII.app downloaded from \"\n        \"https://github.com/johnno1962/InjectionIII/releases\\n\"\n    APP_PREFIX\"And have typed: defaults write com.johnholdsworth.InjectionIII deviceUnlock any\\n\";\n    BOOL isVapor = dlsym(RTLD_DEFAULT, VAPOR_SYMBOL) != nullptr;\n#if TARGET_IPHONE_SIMULATOR || TARGET_OS_OSX\n#if 0 && !defined(INJECTION_III_APP)\n    BOOL isiOSAppOnMac = false;\n    if (@available(iOS 14.0, *)) {\n        isiOSAppOnMac = [NSProcessInfo processInfo].isiOSAppOnMac;\n    }\n    if (!isiOSAppOnMac && !isVapor && !getenv(INJECTION_DAEMON))\n        if (Class standalone = objc_getClass(\"StandaloneInjection\")) {\n            [[standalone new] run];\n            return;\n        }\n#endif\n#elif TARGET_OS_IPHONE\n    const char *envHost = getenv(INJECTION_HOST);\n    if (envHost)\n        [clientClass backgroundConnect:envHost];\n    #ifdef DEVELOPER_HOST\n    [clientClass backgroundConnect:DEVELOPER_HOST];\n    if (!isdigit(DEVELOPER_HOST[0]) && !envHost)\n        printf(APP_PREFIX\"Sending broadcast packet to connect to your development host %s.\\n\"\n               APP_PREFIX\"If this fails, hardcode your Mac's IP address in HotReloading/Package.swift\\n\"\n               \"   or add an environment variable \" INJECTION_HOST\n               \" with this value.\\n%s\", DEVELOPER_HOST, buildPhase);\n    #endif\n    if (!(@available(iOS 14.0, *) && [NSProcessInfo processInfo].isiOSAppOnMac))\n        injectionHost = [clientClass\n        getMulticastService:HOTRELOADING_MULTICAST port:HOTRELOADING_PORT\n                    message:APP_PREFIX\"Connecting to %s (%s)...\\n\"];\n    socketAddr = [injectionHost stringByAppendingString:@HOTRELOADING_PORT];\n    if (injectionClient)\n        return;\n#endif\n    for (int retry=0, retrys=1; retry<retrys; retry++) {\n        if (retry)\n            [NSThread sleepForTimeInterval:1.0];\n        if (SimpleSocket *client = [clientClass connectTo:socketAddr]) {\n            dispatch_once(&onlyOneClient, ^{\n                [injectionClient = client run];\n            });\n            return;\n        }\n    }\n\n#if TARGET_IPHONE_SIMULATOR || TARGET_OS_OSX\n    BOOL usingInjectPackage = dlsym(RTLD_DEFAULT, \"$s6Inject17InjectionObserverCN\") != nullptr;\n    if ((usingInjectPackage || getenv(INJECTION_DAEMON)) &&\n        !getenv(INJECTION_STANDALONE)) {\n        if (usingInjectPackage)\n        printf(APP_PREFIX\"Not falling back to standalone HotReloading as you are using the ‘Inject’ package. \"\n               \"Use MenuBar app to control Injection status or opt in by using INJECTION_STANDALONE env var.\\n\");\n        return;\n    }\n    else if (Class standalone = objc_getClass(\"StandaloneInjection\")) {\n        printf(APP_PREFIX\"Unable to connect to InjectionIII app, falling back to standalone HotReloading.\\n\");\n        [[standalone new] run];\n        return;\n    }\n#endif\n\n    if (isVapor) {\n        printf(APP_PREFIX\"Unable to connect to HotReloading server, \"\n               \"please run %s/start_daemon.sh from inside the project \"\n               \"root and restart the server.\\n\",\n               @__FILE__.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent\n               .stringByDeletingLastPathComponent.UTF8String);\n        return;\n    }\n#ifdef INJECTION_III_APP\n    printf(APP_PREFIX\"⚠️ Injection bundle loaded but could not connect. Is InjectionIII.app running?\\n\");\n#else\n    printf(APP_PREFIX\"⚠️ HotReloading loaded but could not connect to %s. Is InjectionIII.app running? ⚠️\\n\", injectionHost.UTF8String);\n#endif\n#ifndef __IPHONE_OS_VERSION_MIN_REQUIRED\n    printf(APP_PREFIX\"⚠️ For a macOS app you need to turn off the sandbox to connect. ⚠️\\n\");\n#endif\n}\n\n+ (const char *)connectedAddress {\n    return injectionHost.UTF8String;\n}\n@end\n\n#if DEBUG && !defined(INJECTION_III_APP)\n@implementation NSObject(InjectionTester)\n- (void)swiftTraceInjectionTest:(NSString * _Nonnull)sourceFile\n                         source:(NSString * _Nonnull)source {\n    if (!injectionClient)\n        NSLog(@\"swiftTraceInjectionTest: Too early.\");\n    [injectionClient writeCommand:InjectionTestInjection\n                       withString:sourceFile];\n    [injectionClient writeString:source];\n}\n@end\n#endif\n\n@interface NSObject(QuickSpecs)\n+ (id)sharedWorld;\n+ (XCTestSuite *)defaultTestSuite;\n- (void)setCurrentExampleMetadata:(id)md;\n@end\n\n@implementation NSObject (RunXCTestCase)\n+ (void)runXCTestCase:(Class)aTestCase {\n    Class _XCTestSuite = objc_getClass(\"XCTestSuite\");\n    XCTestSuite *suite0 = [_XCTestSuite testSuiteWithName: @\"InjectedTest\"];\n    XCTestSuite *suite = aTestCase.defaultTestSuite;\n    Class _XCTestSuiteRun = objc_getClass(\"XCTestSuiteRun\");\n    XCTestSuiteRun *tr = [_XCTestSuiteRun testRunWithTest: suite];\n    [suite0 addTest:suite];\n    [suite0 performTest:tr];\n    if (NSUInteger failed = tr.totalFailureCount)\n        printf(\"\\n\" APP_PREFIX\"*** %lu/%lu tests have FAILED ***\\n\",\n               failed, tr.testCaseCount);\n//    Class _QuickSpec = objc_getClass(\"QuickSpec\");\n//    Class _QuickWorld = objc_getClass(\"_TtC5Quick5World\");\n//    if (_QuickSpec && [[aTestCase class] isSubclassOfClass:_QuickSpec] &&\n//        [_QuickWorld respondsToSelector:@selector(sharedWorld)] &&\n//        [_QuickWorld instancesRespondToSelector:@selector(setCurrentExampleMetadata:)])\n//        [[_QuickWorld sharedWorld] setCurrentExampleMetadata:nil];\n    printf(\"\\n\");\n}\n@end\n#endif\n\n#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_OS_WATCH\n@interface UIViewController (StoryboardInjection)\n- (void)_loadViewFromNibNamed:(NSString *)a0 bundle:(NSBundle *)a1;\n@end\n@implementation UIViewController (iOS14StoryboardInjection)\n- (void)iOS14LoadViewFromNibNamed:(NSString *)nibName bundle:(NSBundle *)bundle {\n    if ([self respondsToSelector:@selector(_loadViewFromNibNamed:bundle:)])\n        [self _loadViewFromNibNamed:nibName bundle:bundle];\n    else {\n        size_t vcSize = class_getInstanceSize([UIViewController class]);\n        size_t mySize = class_getInstanceSize([self class]);\n        char *extra = (char *)(__bridge void *)self + vcSize;\n        NSData *ivars = [NSData dataWithBytes:extra length:mySize-vcSize];\n        (void)[self initWithNibName:nibName bundle:bundle];\n        memcpy(extra, ivars.bytes, ivars.length);\n        [self loadView];\n    }\n}\n@end\n\n@interface NSObject (Remapped)\n+ (void)addMappingFromIdentifier:(NSString *)identifier toObject:(id)object forCoder:(id)coder;\n+ (id)mappedObjectForCoder:(id)decoder withIdentifier:(NSString *)identifier;\n@end\n\n@implementation NSObject (Remapper)\n\nstatic struct {\n    NSMutableDictionary *inputIndexes;\n    NSMutableArray *output, *order;\n    int orderIndex;\n} remapper;\n\n+ (void)my_addMappingFromIdentifier:(NSString *)identifier toObject:(id)object forCoder:(id)coder {\n    //NSLog(@\"Map %@ = %@\", identifier, object);\n    if(remapper.output && [identifier hasPrefix:@\"UpstreamPlaceholder-\"]) {\n        if (remapper.inputIndexes)\n            remapper.inputIndexes[identifier] = @([remapper.inputIndexes count]);\n        else\n            [remapper.output addObject:object];\n    }\n    [self my_addMappingFromIdentifier:identifier toObject:object forCoder:coder];\n}\n\n+ (id)my_mappedObjectForCoder:(id)decoder withIdentifier:(NSString *)identifier {\n    //NSLog(@\"Mapped? %@\", identifier);\n    if(remapper.output && [identifier hasPrefix:@\"UpstreamPlaceholder-\"]) {\n        if (remapper.inputIndexes)\n            [remapper.order addObject:remapper.inputIndexes[identifier] ?: @\"\"];\n        else\n            return remapper.output[[remapper.order[remapper.orderIndex++] intValue]];\n    }\n    return [self my_mappedObjectForCoder:decoder withIdentifier:identifier];\n}\n\n+ (BOOL)injectUI:(NSString *)changed {\n    static NSMutableDictionary *allOrder;\n    static dispatch_once_t once;\n    printf(APP_PREFIX\"Waiting for rebuild of %s\\n\", changed.UTF8String);\n\n    dispatch_once(&once, ^{\n        Class proxyClass = objc_getClass(\"UIProxyObject\");\n        method_exchangeImplementations(\n           class_getClassMethod(proxyClass,\n                                @selector(my_addMappingFromIdentifier:toObject:forCoder:)),\n           class_getClassMethod(proxyClass,\n                                @selector(addMappingFromIdentifier:toObject:forCoder:)));\n        method_exchangeImplementations(\n           class_getClassMethod(proxyClass,\n                                @selector(my_mappedObjectForCoder:withIdentifier:)),\n           class_getClassMethod(proxyClass,\n                                @selector(mappedObjectForCoder:withIdentifier:)));\n        allOrder = [NSMutableDictionary new];\n    });\n\n    @try {\n        UIViewController *rootViewController = [UIApplication sharedApplication].windows.firstObject.rootViewController;\n        UINavigationController *navigationController = (UINavigationController*)rootViewController;\n        UIViewController *visibleVC = rootViewController;\n\n        if (UIViewController *child =\n            visibleVC.childViewControllers.firstObject)\n            visibleVC = child;\n        if ([visibleVC respondsToSelector:@selector(viewControllers)])\n            visibleVC = [(UISplitViewController *)visibleVC\n                         viewControllers].lastObject;\n\n        if ([visibleVC respondsToSelector:@selector(visibleViewController)])\n            visibleVC = [(UINavigationController *)visibleVC\n                         visibleViewController];\n        if (!visibleVC.nibName && [navigationController respondsToSelector:@selector(topViewController)]) {\n          visibleVC = [navigationController topViewController];\n        }\n\n        NSString *nibName = visibleVC.nibName;\n\n        if (!(remapper.order = allOrder[nibName])) {\n            remapper.inputIndexes = [NSMutableDictionary new];\n            remapper.output = [NSMutableArray new];\n            allOrder[nibName] = remapper.order = [NSMutableArray new];\n\n            [visibleVC iOS14LoadViewFromNibNamed:visibleVC.nibName\n                                          bundle:visibleVC.nibBundle];\n\n            remapper.inputIndexes = nil;\n            remapper.output = nil;\n        }\n\n        Class SwiftEval = objc_getClass(\"SwiftEval\"),\n         SwiftInjection = objc_getClass(\"SwiftInjection\");\n\n        NSError *err = nil;\n        [[SwiftEval sharedInstance] rebuildWithStoryboard:changed error:&err];\n        if (err)\n            return FALSE;\n\n        void (^resetRemapper)(void) = ^{\n            remapper.output = [NSMutableArray new];\n            remapper.orderIndex = 0;\n        };\n\n        resetRemapper();\n\n        [visibleVC iOS14LoadViewFromNibNamed:visibleVC.nibName\n                                      bundle:visibleVC.nibBundle];\n\n        if ([[SwiftEval sharedInstance] vaccineEnabled] == YES) {\n            resetRemapper();\n            [SwiftInjection vaccine:visibleVC];\n        } else {\n            [visibleVC viewDidLoad];\n            [visibleVC viewWillAppear:NO];\n            [visibleVC viewDidAppear:NO];\n\n            [SwiftInjection flash:visibleVC];\n        }\n    }\n    @catch(NSException *e) {\n        printf(\"Problem reloading nib: %s\\n\", e.reason.UTF8String);\n    }\n\n    remapper.output = nil;\n    return true;\n}\n@end\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloadingGuts/SimpleSocket.mm",
    "content": "//\n//  SimpleSocket.mm\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloadingGuts/SimpleSocket.mm#63 $\n//\n//  Server and client primitives for networking through sockets\n//  more esailly written in Objective-C than Swift. Subclass to\n//  implement service or client that runs on a background thread\n//  implemented by overriding the \"runInBackground\" method.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#import \"SimpleSocket.h\"\n\n#include <sys/socket.h>\n#include <netinet/tcp.h>\n#include <net/if.h>\n#include <ifaddrs.h>\n#include <netdb.h>\n\n#if 0\n#define SLog NSLog\n#else\n#define SLog while(0) NSLog\n#endif\n\n#define MAX_PACKET 16384\n\ntypedef union {\n    struct {\n        __uint8_t       sa_len;         /* total length */\n        sa_family_t     sa_family;      /* [XSI] address family */\n    };\n    struct sockaddr_storage any;\n    struct sockaddr_in ip4;\n    struct sockaddr addr;\n} sockaddr_union;\n\n@implementation SimpleSocket\n\n+ (int)error:(NSString *)message {\n    NSLog([@\"%@/\" stringByAppendingString:message],\n          self, strerror(errno));\n    return -1;\n}\n\n+ (void)startServer:(NSString *)address {\n    [self performSelectorInBackground:@selector(runServer:) withObject:address];\n}\n\n+ (void)forEachInterface:(void (^)(ifaddrs *ifa, in_addr_t addr, in_addr_t mask))handler {\n    ifaddrs *addrs;\n    if (getifaddrs(&addrs) < 0) {\n        [self error:@\"Could not getifaddrs: %s\"];\n        return;\n    }\n    for (ifaddrs *ifa = addrs; ifa; ifa = ifa->ifa_next)\n        if (ifa->ifa_addr->sa_family == AF_INET)\n            handler(ifa, ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr,\n                    ((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr);\n    freeifaddrs(addrs);\n}\n\n+ (void)runServer:(NSString *)address {\n    sockaddr_union serverAddr;\n    [self parseV4Address:address into:&serverAddr.any];\n\n    int serverSocket = [self newSocket:serverAddr.sa_family];\n    if (serverSocket < 0)\n        return;\n\n    if (bind(serverSocket, &serverAddr.addr, serverAddr.sa_len) < 0)\n        [self error:@\"Could not bind service socket: %s\"];\n    else if (listen(serverSocket, 5) < 0)\n        [self error:@\"Service socket would not listen: %s\"];\n    else\n        while (TRUE) {\n            sockaddr_union clientAddr;\n            socklen_t addrLen = sizeof clientAddr;\n\n            int clientSocket = accept(serverSocket, &clientAddr.addr, &addrLen);\n            if (clientSocket > 0) {\n                int yes = 1;\n                if (setsockopt(clientSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0)\n                    [self error:@\"Could not set SO_NOSIGPIPE: %s\"];\n                @autoreleasepool {\n                    struct sockaddr_in *v4Addr = &clientAddr.ip4;\n                    NSLog(@\"Connection from %s:%d\\n\",\n                          inet_ntoa(v4Addr->sin_addr), ntohs(v4Addr->sin_port));\n                    SimpleSocket *client = [[self alloc] initSocket:clientSocket];\n                    client.isLocalClient =\n                        v4Addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK);\n                    [self forEachInterface:^(ifaddrs *ifa, in_addr_t addr, in_addr_t mask) {\n                        if (v4Addr->sin_addr.s_addr == addr)\n                            client.isLocalClient = TRUE;\n                    }];\n                    [client run];\n                }\n            }\n            else\n                [NSThread sleepForTimeInterval:.5];\n        }\n}\n\n+ (instancetype)connectTo:(NSString *)address {\n    sockaddr_union serverAddr;\n    [self parseV4Address:address into:&serverAddr.any];\n\n    int clientSocket = [self newSocket:serverAddr.sa_family];\n    if (clientSocket < 0)\n        return nil;\n\n    if (connect(clientSocket, &serverAddr.addr, serverAddr.sa_len) < 0) {\n        [self error:@\"Could not connect: %s\"];\n        return nil;\n    }\n\n    return [[self alloc] initSocket:clientSocket];\n}\n\n+ (int)newSocket:(sa_family_t)addressFamily {\n    int newSocket, yes = 1;\n    if ((newSocket = socket(addressFamily, SOCK_STREAM, 0)) < 0)\n        [self error:@\"Could not open service socket: %s\"];\n    else if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0)\n        [self error:@\"Could not set SO_REUSEADDR: %s\"];\n    else if (setsockopt(newSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0)\n        [self error:@\"Could not set SO_NOSIGPIPE: %s\"];\n    else if (setsockopt(newSocket, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes) < 0)\n        [self error:@\"Could not set TCP_NODELAY: %s\"];\n    else if (fcntl(newSocket, F_SETFD, FD_CLOEXEC) < 0)\n        [self error:@\"Could not set FD_CLOEXEC: %s\"];\n    else\n        return newSocket;\n    return -1;\n}\n\n/**\n * Available formats\n * @\"<host>[:<port>]\"\n * where <host> can be NNN.NNN.NNN.NNN or hostname, empty for localhost or * for all interfaces\n * The default port is 80 or a specific number to bind or an empty string to allocate any port\n */\n+ (BOOL)parseV4Address:(NSString *)address into:(struct sockaddr_storage *)serverAddr {\n    NSArray<NSString *> *parts = [address componentsSeparatedByString:@\":\"];\n\n    struct sockaddr_in *v4Addr = (struct sockaddr_in *)serverAddr;\n    bzero(v4Addr, sizeof *v4Addr);\n\n    v4Addr->sin_family = AF_INET;\n    v4Addr->sin_len = sizeof *v4Addr;\n    v4Addr->sin_port = htons(parts.count > 1 ? parts[1].intValue : 80);\n\n    const char *host = parts[0].UTF8String;\n\n    if (!host[0])\n        v4Addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n    else if (host[0] == '*')\n        v4Addr->sin_addr.s_addr = htonl(INADDR_ANY);\n    else if (isdigit(host[0]))\n        v4Addr->sin_addr.s_addr = inet_addr(host);\n    else if (struct hostent *hp = gethostbyname2(host, v4Addr->sin_family))\n        memcpy(&v4Addr->sin_addr, hp->h_addr, hp->h_length);\n    else {\n        [self error:[NSString stringWithFormat:@\"Unable to look up host for %@\", address]];\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n- (instancetype)initSocket:(int)socket {\n    if ((self = [super init])) {\n        clientSocket = socket;\n    }\n    return self;\n}\n\n- (void)run {\n    [self performSelectorInBackground:@selector(runInBackground) withObject:nil];\n}\n\n- (void)runInBackground {\n    [[self class] error:@\"-[SimpleSocket runInBackground] not implemented in subclass\"];\n}\n\ntypedef ssize_t (*io_func)(int, void *, size_t);\n\n- (BOOL)perform:(io_func)io ofBytes:(const void *)buffer\n         length:(size_t)length cmd:(SEL)cmd {\n    size_t bytes, ptr = 0;\n    SLog(@\"#%d %s %lu [%p] %s\", clientSocket, io == read ?\n         \"<-\" : \"->\", length, buffer, sel_getName(cmd));\n    while (ptr < length && (bytes = io(clientSocket,\n        (char *)buffer+ptr, MIN(length-ptr, MAX_PACKET))) > 0)\n        ptr += bytes;\n    if (ptr < length) {\n        NSLog(@\"[%@ %s:%p length:%lu] error: %lu %s\",\n              self, sel_getName(cmd), buffer, length, ptr, strerror(errno));\n        return FALSE;\n    }\n    return TRUE;\n}\n\n- (BOOL)readBytes:(void *)buffer length:(size_t)length cmd:(SEL)cmd {\n    return [self perform:read ofBytes:buffer length:length cmd:cmd];\n}\n\n- (int)readInt {\n    int32_t anint = ~0;\n    if (![self readBytes:&anint length:sizeof anint cmd:_cmd])\n        return ~0;\n    SLog(@\"#%d <- %d\", clientSocket, anint);\n    return anint;\n}\n\n- (void *)readPointer {\n    void *aptr = (void *)~0;\n    if (![self readBytes:&aptr length:sizeof aptr cmd:_cmd])\n        return aptr;\n    SLog(@\"#%d <- %p\", clientSocket, aptr);\n    return aptr;\n}\n\n- (NSData *)readData {\n    size_t length = [self readInt];\n    void *bytes = malloc(length);\n    if (!bytes || ![self readBytes:bytes length:length cmd:_cmd])\n        return nil;\n    return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES];\n}\n\n- (NSString *)readString {\n    NSString *str = [[NSString alloc] initWithData:[self readData]\n                                          encoding:NSUTF8StringEncoding];\n    SLog(@\"#%d <- %d '%@'\", clientSocket, (int)str.length, str);\n    return str;\n}\n\n- (BOOL)writeBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd {\n    return [self perform:(io_func)write ofBytes:buffer length:length cmd:cmd];\n}\n\n- (BOOL)writeInt:(int)length {\n    SLog(@\"#%d %d ->\", clientSocket, length);\n    return [self writeBytes:&length length:sizeof length cmd:_cmd];\n}\n\n- (BOOL)writePointer:(void *)ptr {\n    SLog(@\"#%d %p ->\", clientSocket, ptr);\n    return [self writeBytes:&ptr length:sizeof ptr cmd:_cmd];\n}\n\n- (BOOL)writeData:(NSData *)data {\n    uint32_t length = (uint32_t)data.length;\n    SLog(@\"#%d [%d] ->\", clientSocket, length);\n    return [self writeInt:length] &&\n        [self writeBytes:data.bytes length:length cmd:_cmd];\n}\n\n- (BOOL)writeString:(NSString *)string {\n    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];\n    SLog(@\"#%d %d '%@' ->\", clientSocket, (int)data.length, string);\n    return [self writeData:data];\n}\n\n- (BOOL)writeCommand:(int)command withString:(NSString *)string {\n    return [self writeInt:command] &&\n        (!string || [self writeString:string]);\n}\n\n- (void)dealloc {\n    close(clientSocket);\n}\n\n/// Hash used to differentiate HotReloading users on network.\n/// Derived from path to source file in project's DerivedData.\n+ (int)multicastHash {\n    #ifdef INJECTION_III_APP\n    const char *key = [[NSBundle bundleForClass:self]\n        .infoDictionary[@\"UserHome\"] UTF8String] ?:\n        NSHomeDirectory().UTF8String;\n    #else\n    NSString *file = [NSString stringWithUTF8String:__FILE__];\n    const char *key = [file\n       stringByReplacingOccurrencesOfString: @\"(/Users/[^/]+).*\"\n           withString: @\"$1\" options: NSRegularExpressionSearch\n               range: NSMakeRange(0, file.length)].UTF8String;\n    #endif\n    int hash = 0;\n    for (size_t i=0, len = strlen(key); i<len; i++)\n        hash = hash*5 ^ (i+3)%15*key[i];\n    return hash;\n}\n\nstruct multicast_socket_packet {\n    int version, hash;\n    char host[256];\n};\n\n/// Used for HotReloading clients to find their controlling Mac.\n/// @param multicast MULTICAST address to use\n/// @param port Port identifier of form \":NNNN\"\n+ (void)multicastServe:(const char *)multicast port:(const char *)port {\n    #ifdef DEVELOPER_HOST\n    if (isdigit(DEVELOPER_HOST[0]))\n        return;\n    #endif\n\n    struct sockaddr_in addr;\n    memset(&addr, 0, sizeof addr);\n    addr.sin_family = AF_INET;\n    addr.sin_addr.s_addr = htonl(INADDR_ANY); /* N.B.: differs from sender */\n    if (const char *colon = index(port, ':'))\n        port = colon+1;\n    addr.sin_port = htons(atoi(port));\n\n    /* create what looks like an ordinary UDP socket */\n    int multicastSocket, yes = 1;\n    if ((multicastSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)\n        [self error:@\"Could not get mutlicast socket: %s\"];\n    else if (fcntl(multicastSocket, F_SETFD, FD_CLOEXEC) < 0)\n        [self error:@\"Could not set close exec: %s\"];\n    else if (setsockopt(multicastSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0)\n        [self error:@\"Could not reuse mutlicast socket addr: %s\"];\n    else if (bind(multicastSocket, (struct sockaddr *)&addr, sizeof addr) < 0)\n        [self error:@\"Could not bind mutlicast socket addr: %s. \"\n         \"Once this starts occuring, a reboot may be necessary. \"\n         \"Or, you can hardcode the IP address of your Mac as the \"\n         \"the value for 'hostname' in HotReloading/Package.swift.\"];\n    else\n        [self performSelectorInBackground:@selector(multicastListen:)\n                               withObject:[NSNumber numberWithInt:multicastSocket]];\n}\n\n/// Listens for clients looking to connect and if the hash matches, replies.\n/// @param socket Multicast socket as NSNumber\n+ (void)multicastListen:(NSNumber *)socket {\n    int multicastSocket = [socket intValue];\n    while (multicastSocket) {\n        struct sockaddr_in addr;\n        socklen_t addrlen = sizeof addr;\n        struct multicast_socket_packet msgbuf;\n\n        if (recvfrom(multicastSocket, &msgbuf, sizeof msgbuf, 0,\n                     (struct sockaddr *)&addr, &addrlen) < sizeof msgbuf) {\n            [self error:@\"Could not receive from multicast: %s\"];\n            sleep(1);\n            continue;\n        }\n\n        NSLog(@\"%@: Multicast recvfrom %s (%s) %u c.f. %u\\n\",\n              self, msgbuf.host, inet_ntoa(addr.sin_addr),\n              [self multicastHash], msgbuf.hash);\n\n        gethostname(msgbuf.host, sizeof msgbuf.host);\n        if ([self multicastHash] == msgbuf.hash &&\n            sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0,\n                   (struct sockaddr *)&addr, addrlen) < sizeof msgbuf) {\n            [self error:@\"Could not send to multicast: %s\"];\n            sleep(1);\n        }\n    }\n}\n\n/// Client end of multicast means of determining address of server\n/// @param multicast Multicast IP address to use.\n/// @param port Port number as string.\n/// @param format Format for connecting message.\n+ (NSString *)getMulticastService:(const char *)multicast\n    port:(const char *)port message:(const char *)format {\n    #ifdef DEVELOPER_HOST\n    if (isdigit(DEVELOPER_HOST[0]))\n        return @DEVELOPER_HOST;\n    #else\n    #define DEVELOPER_HOST \"127.0.0.1\"\n    #endif\n\n    static struct sockaddr_in addr;\n    addr.sin_family = AF_INET;\n    addr.sin_addr.s_addr = htonl(INADDR_ANY);\n    if (const char *colon = index(port, ':'))\n        port = colon+1;\n    addr.sin_port = 0;\n\n    // For a real device, we have to use multicast\n    // to locate the developer's Mac to connect to.\n    int multicastSocket, yes = 1;\n    if ((multicastSocket = socket(addr.sin_family, SOCK_DGRAM, 0)) < 0) {\n        [self error:@\"Could not get broadcast socket: %s\"];\n        return @DEVELOPER_HOST;\n    }\n    if (setsockopt(multicastSocket, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes) < 0) {\n        [self error:@\"Could not setsockopt: %s\"];\n        close(multicastSocket);\n        return @DEVELOPER_HOST;\n    }\n\n    struct multicast_socket_packet msgbuf;\n    msgbuf.version = 1;\n    msgbuf.hash = [self multicastHash];\n    gethostname(msgbuf.host, sizeof msgbuf.host);\n\n    addr.sin_port = htons(atoi(port));\n    [self forEachInterface:^(ifaddrs *ifa, in_addr_t laddr, in_addr_t nmask) {\n        switch (ntohl(laddr) >> 24) {\n            case 10: // mobile network\n//            case 172: // hotspot\n            case 127: // loopback\n                return;\n        }\n        int idx = if_nametoindex(ifa->ifa_name);\n        setsockopt(multicastSocket, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof idx);\n        addr.sin_addr.s_addr = laddr | ~nmask;\n        printf(\"Broadcasting to %s.%d:%s to locate InjectionIII host...\\n\",\n               ifa->ifa_name, idx, inet_ntoa(addr.sin_addr));\n        if (sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0,\n                   (struct sockaddr *)&addr, sizeof addr) < 0)\n            [self error:@\"Could not send broadcast ping: %s\"];\n    }];\n\n    socklen_t addrlen = sizeof addr;\n    while (recvfrom(multicastSocket, &msgbuf, sizeof msgbuf, 0,\n                    (struct sockaddr *)&addr, &addrlen) < sizeof msgbuf) {\n        [self error:@\"%s: Error receiving from broadcast: %s\"];\n        sleep(1);\n    }\n\n    const char *ipaddr = inet_ntoa(addr.sin_addr);\n    printf(format, msgbuf.host, ipaddr);\n    close(multicastSocket);\n    return [NSString stringWithUTF8String:ipaddr];\n}\n\n@end\n#endif\n"
  },
  {
    "path": "Sources/HotReloadingGuts/Unhide.mm",
    "content": "//\n//  Unhide.mm\n//\n//  Created by John Holdsworth on 07/03/2021.\n//\n//  Removes \"hidden\" visibility for certain Swift symbols\n//  (default argument generators) so they can be referenced\n//  in a file being dynamically loaded.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloadingGuts/Unhide.mm#52 $\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#import <Foundation/Foundation.h>\n\n#import <mach-o/loader.h>\n#import <mach-o/nlist.h>\n#import <mach-o/stab.h>\n#import <sys/stat.h>\n#import <algorithm>\n#import <string>\n#import <vector>\n#import <map>\n\n#import \"InjectionClient.h\"\n\nstatic std::map<std::string,int> seen;\n\nstatic const char *strend(const char *str) {\n    return str + strlen(str);\n}\n\nvoid unhide_reset(void) {\n    seen.clear();\n}\n\nint unhide_symbols(const char *framework, const char *linkFileList, FILE *log, time_t since) {\n    FILE *linkFiles = fopen(linkFileList, \"r\");\n    if (!linkFiles) {\n       fprintf(log, \"unhide: Could not open link file list %s\\n\", linkFileList);\n       return -1;\n    }\n\n    char buffer[PATH_MAX];\n    __block int totalExported = 0;\n\n    while (fgets(buffer, sizeof buffer, linkFiles)) {\n        buffer[strlen(buffer)-1] = '\\000';\n        @autoreleasepool {\n            totalExported += unhide_object(buffer, framework, log, nil, nil);\n        }\n    }\n\n    fclose(linkFiles);\n    return totalExported;\n}\n\ntypedef BOOL (^ _Nonnull STSymbolFilter)(const char *_Nonnull symname);\n\n@interface NSObject(SwiftTrace)\n@property (nonatomic, class, copy) STSymbolFilter _Nonnull swiftTraceSymbolFilter;\n+ (NSString *_Nullable)swiftTraceDemangle:(char const *_Nonnull)symbol;\n@end\n\n#define SWIFT_PRIVATE 0xe\n#define SWIFT_GLOBAL 0xf\n\n/// Unhiding is where symbols with \"private extenal\" visibility\n/// are exported so they will be available when a dynamic\n/// library is loaded. This was originally encountered for\n/// default argument generators but can also be useful\n/// for the addressors of private top level and static vars.\n/// @param object_file Path to object file\n/// @param framework Name of image containing object file\n/// @param log FILE * to log to\n/// @param class_references return references to objective-c classes so they can be fixed up.\n/// @param descriptor_refs return local varibles prefixed with l_got. that need to be fixed up.\nint unhide_object(const char *object_file, const char *framework, FILE *log,\n                  NSMutableArray<NSString *> *class_references,\n                  NSMutableArray<NSString *> *descriptor_refs) {\n//            struct stat info;\n//            if (stat(buffer, &info) || info.st_mtimespec.tv_sec < since)\n//                continue;\n            NSString *file = [NSString stringWithUTF8String:object_file];\n            NSData *patched = [[NSMutableData alloc] initWithContentsOfFile:file];\n\n            if (!patched) {\n                fprintf(log, \"unhide: Could not read %s\\n\", [file UTF8String]);\n                return 0;\n            }\n\n            struct mach_header_64 *object = (struct mach_header_64 *)[patched bytes];\n            const char *filename = file.lastPathComponent.UTF8String;\n\n            if (object->magic != MH_MAGIC_64) {\n                fprintf(log, \"unhide: Invalid magic 0x%x != 0x%x (bad arch?)\\n\",\n                        object->magic, MH_MAGIC_64);\n                return 0;\n            }\n\n            struct symtab_command *symtab = NULL;\n            struct dysymtab_command *dylib = NULL;\n\n            for (struct load_command *cmd = (struct load_command *)((char *)object + sizeof *object) ;\n                 cmd < (struct load_command *)((char *)object + object->sizeofcmds) ;\n                 cmd = (struct load_command *)((char *)cmd + cmd->cmdsize)) {\n\n                if (cmd->cmd == LC_SYMTAB)\n                    symtab = (struct symtab_command *)cmd;\n                else if (cmd->cmd == LC_DYSYMTAB)\n                    dylib = (struct dysymtab_command *)cmd;\n            }\n\n            if (!symtab || !dylib) {\n                fprintf(log, \"unhide: Missing symtab or dylib cmd %s: %p & %p\\n\",\n                        filename, symtab, dylib);\n                return 0;\n            }\n            struct nlist_64 *all_symbols64 = (struct nlist_64 *)((char *)object + symtab->symoff);\n#if 1\n            struct nlist_64 *end_symbols64 = all_symbols64 + symtab->nsyms;\n            int exported = 0;\n\n//            dylib->iextdefsym -= dylib->nlocalsym;\n//            dylib->nextdefsym += dylib->nlocalsym;\n//            dylib->nlocalsym = 0;\n#endif\n            size_t isReverseInterpose = class_references ? strlen(framework) : 0;\n            typedef std::pair<uint64_t, const char *> class_pair;\n            std::vector<class_pair> class_refs;\n            for (int i=0 ; i<symtab->nsyms ; i++) {\n                struct nlist_64 &symbol = all_symbols64[i];\n                if (symbol.n_sect == NO_SECT)\n                    continue; // not definition\n                const char *symname = (char *)object + symtab->stroff + symbol.n_un.n_strx;\n\n                if (class_references) {\n                    static char classRef[] = {\"l_OBJC_CLASS_REF_$_\"};\n                    int clasRefSize = sizeof classRef-1;\n                    if (strncmp(symname, classRef, clasRefSize) == 0)\n                        class_refs.push_back({symbol.n_value,\n                            symname + clasRefSize});\n                }\n\n                if (descriptor_refs) {\n                    static char gotPrefix[] = {\"l_got.\"};\n                    int gotPrefixSize = sizeof gotPrefix-1;\n                    if (strncmp(symname, gotPrefix, gotPrefixSize) == 0)\n                        [descriptor_refs addObject:[NSString\n                         stringWithUTF8String:symname + gotPrefixSize]];\n                }\n\n                if (strncmp(symname, \"_$s\", 3) != 0)\n                    continue; // not swift symbol\n\n                // Default argument generators have a suffix ANN_\n                // Covers a few other cases encountred now as well.\n                const char *symend = strend(symname) - 1;\n                BOOL isMutableAddressor = strcmp(symend-2, \"vau\") == 0 ||\n                    // witness table accessor functions...\n                    (strcmp(symend-1, \"Wl\") == 0 &&\n                     strncmp(symname+1, framework, isReverseInterpose) == 0);\n                BOOL isDefaultArgument = (*symend == '_' &&\n                    (symend[-1] == 'A' || (isdigit(symend[-1]) &&\n                    (symend[-2] == 'A' || (isdigit(symend[-2]) &&\n                     symend[-3] == 'A'))))) ||// isMutableAddressor ||\n                    strcmp(symend-1, \"FZ\") == 0 || (symend[-1] == 'M' && (\n                    *symend == 'c' || *symend == 'g' || *symend == 'n'));\n\n                #if DEBUG\n                if (symbol.n_sect != NO_SECT && symbol.n_type == SWIFT_PRIVATE &&\n                    [NSObject respondsToSelector:@selector(swiftTraceSymbolFilter)] &&\n                    !isMutableAddressor && NSObject.swiftTraceSymbolFilter(symname)) {\n                    NSString *demangled = [NSObject swiftTraceDemangle:symname];\n                    if (![demangled hasPrefix:@\"reflection metadata \"])\n                        printf(APP_PREFIX\"%s is private and may not inject\\n\",\n                               demangled.UTF8String);\n                }\n                #endif\n\n//                fprintf(log, \"symbol: #%d 0%lo 0x%x 0x%x %3d %s %d\\n\",\n//                       i, (char *)&symbol.n_type - (char *)object,\n//                       symbol.n_type, symbol.n_desc,\n//                       symbol.n_sect, symname, isDefaultArgument);\n\n                // The following reads: If symbol is for a default argument\n                // and it is the definition (not a reference) and we've not\n                // seen it before and it hadsn't already been \"unhidden\"...\n                if (isReverseInterpose ? isMutableAddressor :\n                    isDefaultArgument && !seen[symname]++ &&\n                    symbol.n_type & N_PEXT) {\n                    symbol.n_type |= N_EXT;\n                    symbol.n_type &= ~N_PEXT;\n                    symbol.n_type = SWIFT_GLOBAL;\n                    symbol.n_desc = N_GSYM;\n\n                    if (!exported++)\n                        fprintf(log, \"%s.%s: local: %d %d ext: %d %d undef: %d %d extref: %d %d indirect: %d %d extrel: %d %d localrel: %d %d symlen: 0%lo\\n\",\n                               framework, filename,\n                               dylib->ilocalsym, dylib->nlocalsym,\n                               dylib->iextdefsym, dylib->nextdefsym,\n                               dylib->iundefsym, dylib->nundefsym,\n                               dylib->extrefsymoff, dylib->nextrefsyms,\n                               dylib->indirectsymoff, dylib->nindirectsyms,\n                               dylib->extreloff, dylib->nextrel,\n                               dylib->locreloff, dylib->nlocrel,\n                               (char *)&end_symbols64->n_un - (char *)object);\n\n                    fprintf(log, \"exported: #%d 0%lo 0x%x 0x%x %3d %s\\n\", i,\n                           (char *)&symbol.n_type - (char *)object,\n                           symbol.n_type, symbol.n_desc,\n                           symbol.n_sect, symname);\n                }\n            }\n\n            if (class_references) {\n                sort(class_refs.begin(), class_refs.end(),\n                     [&] (const class_pair &l, const class_pair &r) {\n                    return l.first < r.first;\n                });\n\n                for (auto &cr : class_refs)\n                    [class_references addObject:[NSString\n                                                 stringWithUTF8String:cr.second]];\n            }\n\n            if (exported && ![patched writeToFile:file atomically:YES])\n                fprintf(log, \"unhide: Could not write %s\\n\", [file UTF8String]);\n            return exported;\n}\n\n#if 0 && TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR // never used\nint unhide_framework(const char *framework, FILE *log) {\n    int totalExported = 0;\n#if 0 // Not implemented\n    @autoreleasepool {\n        NSString *file = [NSString stringWithUTF8String:framework];\n        NSData *patched = [[NSMutableData alloc] initWithContentsOfFile:file];\n\n        if (!patched) {\n            fprintf(log, \"unhide: Could not read %s\\n\", [file UTF8String]);\n            return -1;\n        }\n\n        struct mach_header_64 *object = (struct mach_header_64 *)[patched bytes];\n        const char *filename = file.lastPathComponent.UTF8String;\n\n        if (object->magic != MH_MAGIC_64) {\n            fprintf(log, \"unhide: Invalid magic 0x%x != 0x%x (bad arch?)\\n\",\n                    object->magic, MH_MAGIC_64);\n            return -1;\n        }\n\n        struct symtab_command *symtab = NULL;\n        struct dysymtab_command *dylib = NULL;\n\n        for (struct load_command *cmd = (struct load_command *)((char *)object + sizeof *object) ;\n             cmd < (struct load_command *)((char *)object + object->sizeofcmds) ;\n             cmd = (struct load_command *)((char *)cmd + cmd->cmdsize)) {\n\n            if (cmd->cmd == LC_SYMTAB)\n                symtab = (struct symtab_command *)cmd;\n            else if (cmd->cmd == LC_DYSYMTAB)\n                dylib = (struct dysymtab_command *)cmd;\n        }\n\n        if (!symtab || !dylib) {\n            fprintf(log, \"unhide: Missing symtab or dylib cmd %s: %p & %p\\n\",\n                    filename, symtab, dylib);\n            return -1;\n        }\n        struct nlist_64 *all_symbols64 = (struct nlist_64 *)((char *)object + symtab->symoff);\n#if 1\n        struct nlist_64 *end_symbols64 = all_symbols64 + symtab->nsyms;\n        int exported = 0;\n\n//            dylib->iextdefsym -= dylib->nlocalsym;\n//            dylib->nextdefsym += dylib->nlocalsym;\n//            dylib->nlocalsym = 0;\n#endif\n        for (int i=0 ; i<symtab->nsyms ; i++) {\n            struct nlist_64 &symbol = all_symbols64[i];\n            if (symbol.n_sect == NO_SECT)\n                continue; // not definition\n            const char *symname = (char *)object + symtab->stroff + symbol.n_un.n_strx;\n\n//                printf(\"symbol: #%d 0%lo 0x%x 0x%x %3d %s\\n\", i,\n//                       (char *)&symbol.n_type - (char *)object,\n//                       symbol.n_type, symbol.n_desc,\n//                       symbol.n_sect, symname);\n            if (strncmp(symname, \"_$s\", 3) != 0)\n                continue; // not swift symbol\n\n            // Default argument generators have a suffix ANN_\n            // Covers a few other cases encountred now as well.\n            const char *symend = strend(symname) - 1;\n            BOOL isDefaultArgument = (*symend == '_' &&\n                (symend[-1] == 'A' || (isdigit(symend[-1]) &&\n                (symend[-2] == 'A' || (isdigit(symend[-2]) &&\n                 symend[-3] == 'A'))))) || strcmp(symend-2, \"vau\") == 0 ||\n                strcmp(symend-1, \"FZ\") == 0 || (symend[-1] == 'M' && (\n                *symend == 'c' || *symend == 'g' || *symend == 'n'));\n\n            // The following reads: If symbol is for a default argument\n            // and it is the definition (not a reference) and we've not\n            // seen it before and it hadsn't already been \"unhidden\"...\n            if (isDefaultArgument && !seen[symname]++ &&\n                symbol.n_type & N_PEXT) {\n                symbol.n_type |= N_EXT;\n                symbol.n_type &= ~N_PEXT;\n                symbol.n_type = 0xf;\n                symbol.n_desc = N_GSYM;\n\n                if (!exported++)\n                    fprintf(log, \"%s.%s: local: %d %d ext: %d %d undef: %d %d extref: %d %d indirect: %d %d extrel: %d %d localrel: %d %d symlen: 0%lo\\n\",\n                           framework, filename,\n                           dylib->ilocalsym, dylib->nlocalsym,\n                           dylib->iextdefsym, dylib->nextdefsym,\n                           dylib->iundefsym, dylib->nundefsym,\n                           dylib->extrefsymoff, dylib->nextrefsyms,\n                           dylib->indirectsymoff, dylib->nindirectsyms,\n                           dylib->extreloff, dylib->nextrel,\n                           dylib->locreloff, dylib->nlocrel,\n                           (char *)&end_symbols64->n_un - (char *)object);\n\n                fprintf(log, \"exported: #%d 0%lo 0x%x 0x%x %3d %s\\n\", i,\n                       (char *)&symbol.n_type - (char *)object,\n                       symbol.n_type, symbol.n_desc,\n                       symbol.n_sect, symname);\n            }\n        }\n\n        if (exported && ![patched writeToFile:file atomically:YES])\n            fprintf(log, \"unhide: Could not write %s\\n\", [file UTF8String]);\n        totalExported += exported;\n    }\n#endif\n    return totalExported;\n}\n#endif\n\n#if !TARGET_IPHONE_SIMULATOR\n#import <mach-o/getsect.h>\n#import <mach/vm_param.h>\n#import <sys/mman.h>\n#import <dlfcn.h>\n\nextern \"C\" {\n    // Duplicated from SwiftTrace.h\n    #define ST_LAST_IMAGE -1\n    #define ST_ANY_VISIBILITY 0\n    #define ST_GLOBAL_VISIBILITY 0xf\n    #define ST_HIDDEN_VISIBILITY 0x1e\n    #define ST_LOCAL_VISIBILITY 0xe\n\n    typedef NS_ENUM(uint8_t, STVisibility) {\n        STVisibilityAny = ST_ANY_VISIBILITY,\n        STVisibilityGlobal = ST_GLOBAL_VISIBILITY,\n        STVisibilityHidden = ST_HIDDEN_VISIBILITY,\n        STVisibilityLocal = ST_LOCAL_VISIBILITY,\n    };\n\n    typedef BOOL (^ _Nonnull STSymbolFilter)(const char *_Nonnull symname);\n    /**\n     Callback on selecting symbol.\n     */\n    typedef void (^ _Nonnull STSymbolCallback)(const void *_Nonnull address, const char *_Nonnull symname,\n                                         void *_Nonnull typeref, void *_Nonnull typeend);\n    typedef void (*fast_dlscan_t)(const void *_Nonnull ptr,\n        STVisibility visibility, STSymbolFilter filter, STSymbolCallback callback);\n    typedef void *_Nullable (*fast_dlsym_t)(const void *_Nonnull ptr, const char *_Nonnull symname);\n    typedef int (*fast_dladdr_t)(const void *_Nonnull, Dl_info *_Nonnull);\n    typedef NSString *_Nonnull (*describeImageInfo_t)(const Dl_info *_Nonnull info);\n}\n\n/**\n The last piece of the injecting SwiftUI on a device puzzle.\n Symbolic references are a stream of bytes used to specify\n a complex type. They contain a relative pointer to the moninal\n type information of the component types which we need to switch\n from that newly injected to the original in the app executable.\n This is becuase when we don't use the dynamic linker it seems\n injected type information is not proberly initialised.\n @param image Pointer to pseudo image\n */\nvoid reverse_symbolics(const void *image) {\n    BOOL debug = FALSE;\n    #define RSPREFIX \"reverse_symbolics: ⚠️ \"\n    #define rsprintf if (debug) printf\n    #define MAX_SYMBOLIC_REF 0x1f\n    #define PAGE_ROUND(_sz) (((_sz) + PAGE_SIZE-1) & ~(PAGE_SIZE-1))\n    #define LATE_BIND(f) static f##_t f; if (!f) f = (f##_t)dlsym(RTLD_DEFAULT, #f)\n    LATE_BIND(fast_dlscan);\n    LATE_BIND(fast_dladdr);\n    LATE_BIND(describeImageInfo);\n\n    uint64_t typeref_size = 0;\n    char *typeref_start = getsectdatafromheader_64((mach_header_64 *)image,\n                               SEG_TEXT, \"__swift5_typeref\", &typeref_size);\n    if (mprotect((void *)((uintptr_t)typeref_start&~(PAGE_SIZE-1)),\n                 PAGE_ROUND(typeref_size), PROT_WRITE|PROT_READ) != KERN_SUCCESS)\n        printf(RSPREFIX\"Unable to make %d bytes writable %s\\n\",\n               (int)typeref_size, strerror(errno));\n\n    static char symbolics[] = {\"_symbolic _____\"};\n    fast_dlscan(image, STVisibilityAny, ^BOOL(const char *symname) {\n        return strncmp(symname, symbolics, sizeof symbolics-1) == 0;\n    }, ^(const void * _Nonnull address, const char * _Nonnull symname, void * _Nonnull typeref, void * _Nonnull typeend) {\n//        rsprintf(\"%s\\n\", symname);\n\n//        char buffer[1000], first[100];\n//        const char *prefixPtr = symname + sizeof symbolics - 2;\n//        const char *typesPtr = strchr(prefixPtr, ' ')+1;\n        unsigned char *infoPtr = (unsigned char *)address;\n\n        while (*infoPtr) {\n            if (*infoPtr++ > MAX_SYMBOLIC_REF) {\n                printf(RSPREFIX\"Out of sync?\\n\");\n                break;\n            }\n\n//            const char *typeEnd = strchr(typesPtr, ' ') ?:\n//                                typesPtr + strlen(typesPtr);\n//            snprintf(buffer, sizeof buffer, \"$s%.*sMn\",\n//                     (int)(typeEnd - typesPtr), typesPtr);\n\n            int before = *(int *)infoPtr;\n            const void *referenced = infoPtr + before, *value;\n            Dl_info info;\n\n            if (fast_dladdr(referenced, &info) && info.dli_fbase == image &&\n                strcmp(strend(info.dli_sname) - 2, \"Mn\") == 0 &&\n                (value = dlsym(RTLD_DEFAULT, info.dli_sname))) {\n                ssize_t relative = (unsigned char *)value - infoPtr;\n                *(int *)infoPtr = (int)relative;\n            }\n\n            if (before != *(int *)infoPtr)\n                rsprintf(\"Reversed: %x -> %x %s\\n\", before, *(int *)infoPtr,\n                         describeImageInfo(&info).UTF8String);\n\n            infoPtr += sizeof before;\n            while (*infoPtr > MAX_SYMBOLIC_REF)\n                infoPtr++;\n\n//            static char delim[] = {\"_____\"};\n//            while (strncmp(prefixPtr, delim, sizeof delim-1) != 0)\n//                prefixPtr++;\n//            prefixPtr += sizeof delim-1;\n//\n//            typesPtr = typeEnd;\n//            if (*typesPtr == ' ')\n//                typesPtr++;\n//            else\n//                break;\n        }\n    });\n\n    #if 000\n    static char associateds[] = {\"_associated conformance \"};\n    fast_dlscan(image, STVisibilityAny, ^(const char *symname) {\n        return strncmp(symname, associateds, sizeof associateds-1) == 0;\n    }, ^(const void * _Nonnull address, const char * _Nonnull symname, void * _Nonnull typeref, void * _Nonnull typeend) {\n        unsigned char *infoPtr = (unsigned char *)address;\n        infoPtr += 2;\n\n        void *ptr = infoPtr + *(int *)infoPtr;\n        Dl_info info;\n        fast_dladdr(ptr, &info);\n        printf(\"ASSOC %s\\n\", describeImageInfo(&info).UTF8String);\n\n        int v0 = *(int *)infoPtr;\n        if (image == info.dli_fbase) {\n            extern int main();\n            void *value = fast_dlsym((void *)main, info.dli_sname);\n            printf(\">>>> %s %p %p %p\\n\",\n                   info.dli_sname, image, info.dli_fbase, value);\n            size_t diff = (unsigned char *)value - infoPtr;\n            *(int *)infoPtr = (int)diff;\n        }\n        printf(\"%p -> %p %s\\n\", v0, *(int *)infoPtr,\n               describeImageInfo(&info).UTF8String);\n    });\n    #endif\n\n    if (mprotect((void *)((uintptr_t)typeref_start&~(PAGE_SIZE-1)),\n                 PAGE_ROUND(typeref_size), PROT_EXEC|PROT_READ) != KERN_SUCCESS)\n        printf(RSPREFIX\"Unable to make %d bytes executable %s\\n\",\n               (int)typeref_size, strerror(errno));\n}\n#endif\n#endif\n"
  },
  {
    "path": "Sources/HotReloadingGuts/include/InjectionClient.h",
    "content": "//\n//  InjectionClient.h\n//  InjectionBundle\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloadingGuts/include/InjectionClient.h#68 $\n//\n//  Shared definitions between server and client.\n//\n\n#import <Foundation/Foundation.h>\n#import \"UserDefaults.h\"\n#import <mach-o/dyld.h>\n#ifndef __IPHONE_OS_VERSION_MIN_REQUIRED\n#import <AppKit/NSWorkspace.h>\n#import <libproc.h>\n#import \"../../injectiondGuts/include/Xcode.h\"\n#endif\n\n#define HOTRELOADING_PORT \":8899\"\n#define HOTRELOADING_SALT 2122172543\n#define HOTRELOADING_MULTICAST \"239.255.255.239\"\n\n#ifdef INJECTION_III_APP\n#define INJECTION_ADDRESS \":8898\"\n#import \"../../../../InjectionIII/InjectionIIISalt.h\"\n#define INJECTION_KEY @\"bvijkijyhbtrbrebzjbbzcfbbvvq\"\n#define APP_NAME \"InjectionIII\"\n#define APP_PREFIX \"💉 \"\n#else\n#define INJECTION_ADDRESS HOTRELOADING_PORT\n#define INJECTION_SALT HOTRELOADING_SALT\nextern NSString *INJECTION_KEY;\n#define APP_NAME \"HotReloading\"\n#define APP_PREFIX \"🔥 \"\n#if DEBUG\n@interface NSObject(InjectionTester)\n- (void)swiftTraceInjectionTest:(NSString *)sourceFile\n                         source:(NSString *)source;\n@end\n#endif\n#endif\n\n#define COMMANDS_PORT \":8896\"\n#define DYLIB_PREFIX \"/eval_injection_\" // Was expected by DLKit\n#define VAPOR_SYMBOL \"$s10RoutingKit10ParametersVN\"\n#define FRAMEWORK_DELIMITER @\",\"\n#define CALLORDER_DELIMITER @\"---\"\n\n// The various environment variables\n#define INJECTION_HOST \"INJECTION_HOST\"\n#define INJECTION_DETAIL \"INJECTION_DETAIL\"\n#define INJECTION_PROJECT_ROOT \"INJECTION_PROJECT_ROOT\"\n#define INJECTION_DERIVED_DATA \"INJECTION_DERIVED_DATA\"\n#define INJECTION_DYNAMIC_CAST \"INJECTION_DYNAMIC_CAST\"\n#define INJECTION_PRESERVE_STATICS \"INJECTION_PRESERVE_STATICS\"\n#define INJECTION_SWEEP_DETAIL \"INJECTION_SWEEP_DETAIL\"\n#define INJECTION_SWEEP_EXCLUDE \"INJECTION_SWEEP_EXCLUDE\"\n#define INJECTION_OF_GENERICS \"INJECTION_OF_GENERICS\"\n#define INJECTION_NOGENERICS \"INJECTION_NOGENERICS\"\n#define INJECTION_USEINTESTS \"INJECTION_USEINTESTS\"\n#define INJECTION_UNHIDE \"INJECTION_UNHIDE\"\n#define INJECTION_QUICK_FILES \"INJECTION_QUICK_FILES\"\n#define INJECTION_DIRECTORIES \"INJECTION_DIRECTORIES\"\n#define INJECTION_STANDALONE \"INJECTION_STANDALONE\"\n#define INJECTION_NOKEYPATHS \"INJECTION_NOKEYPATHS\"\n#define INJECTION_KEYPATHS \"INJECTION_KEYPATHS\"\n#define INJECTION_DAEMON \"INJECTION_DAEMON\"\n#define INJECTION_LOOKUP \"INJECTION_LOOKUP\"\n#define INJECTION_REPLAY \"INJECTION_REPLAY\"\n#define INJECTION_TRACE \"INJECTION_TRACE\"\n#define INJECTION_BAZEL \"INJECTION_BAZEL\"\n#define INJECTION_DEBUG \"INJECTION_DEBUG\"\n#define INJECTION_BUNDLE_NOTIFICATION \"INJECTION_BUNDLE_NOTIFICATION\"\n#define INJECTION_METRICS_NOTIFICATION \"INJECTION_METRICS_NOTIFICATION\"\n\n@protocol InjectionReader <NSObject>\n- (BOOL)readBytes:(void *)buffer length:(size_t)length cmd:(SEL)cmd;\n@end\n\n@interface InjectionClientLegacy\n@property BOOL vaccineEnabled;\n+ (InjectionClientLegacy *)sharedInstance;\n- (void)vaccine:object;\n+ (void)flash:vc;\n- (void)rebuildWithStoryboard:(NSString *)changed error:(NSError **)err;\n@end\n\n@interface NSObject(HotReloading)\n+ (void)runXCTestCase:(Class)aTestCase;\n#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED\n+ (BOOL)injectUI:(NSString *)changed;\n#endif\n@end\n\n@interface NSProcessInfo(iOSAppOnMac)\n@property BOOL isiOSAppOnMac;\n@end\n\ntypedef NS_ENUM(int, InjectionCommand) {\n    // commands to Bundle\n    InjectionConnected,\n    InjectionWatching,\n    InjectionLog,\n    InjectionSigned,\n    InjectionLoad,\n    InjectionInject,\n    InjectionIdeProcPath,\n    InjectionXprobe,\n    InjectionEval,\n    InjectionVaccineSettingChanged,\n\n    InjectionTrace,\n    InjectionUntrace,\n    InjectionTraceUI,\n    InjectionTraceUIKit,\n    InjectionTraceSwiftUI,\n    InjectionTraceFramework,\n    InjectionQuietInclude,\n    InjectionInclude,\n    InjectionExclude,\n    InjectionStats,\n    InjectionCallOrder,\n    InjectionFileOrder,\n    InjectionFileReorder,\n    InjectionUninterpose,\n    InjectionFeedback,\n    InjectionLookup,\n    InjectionCounts,\n    InjectionCopy,\n    InjectionPseudoUnlock,\n    InjectionPseudoInject,\n    InjectionObjcClassRefs,\n    InjectionDescriptorRefs,\n    InjectionSetXcodeDev,\n    InjectionAppVersion,\n    InjectionProfileUI,\n\n    InjectionInvalid = 1000,\n\n    InjectionEOF = ~0\n};\n\ntypedef NS_ENUM(int, InjectionResponse) {\n    // responses from bundle\n    InjectionComplete,\n    InjectionPause,\n    InjectionSign,\n    InjectionError,\n    InjectionFrameworkList,\n    InjectionCallOrderList,\n    InjectionScratchPointer,\n    InjectionTestInjection,\n    InjectionLegacyUnhide,\n    InjectionForceUnhide,\n    InjectionProjectRoot,\n    InjectionGetXcodeDev,\n    InjectionBuildCache,\n    InjectionDerivedData,\n    InjectionPlatform,\n\n    InjectionExit = ~0\n};\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n// defined in Unhide.mm\nextern int unhide_symbols(const char *framework, const char *linkFileList, FILE *log, time_t since);\nextern int unhide_object(const char *object_file, const char *framework, FILE *log,\n                         NSMutableArray<NSString *> *class_references,\n                         NSMutableArray<NSString *> *descriptor_refs);\nextern void unhide_reset(void);\n\n#if !TARGET_IPHONE_SIMULATOR\nextern void reverse_symbolics(const void *image);\n#endif\n\n// objc4-internal.h\nstruct objc_image_info;\nOBJC_EXPORT Class objc_readClassPair(Class cls,\n                                     const struct objc_image_info *info)\n    OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0);\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "Sources/HotReloadingGuts/include/SimpleSocket.h",
    "content": "//\n//  SimpleSocket.h\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/HotReloadingGuts/include/SimpleSocket.h#16 $\n//\n\n#import <Foundation/Foundation.h>\n#import <arpa/inet.h>\n\n@interface SimpleSocket : NSObject {\n@protected\n    int clientSocket;\n}\n\n@property BOOL isLocalClient;\n\n+ (void)startServer:(NSString *_Nonnull)address;\n+ (void)runServer:(NSString *_Nonnull)address;\n+ (int)error:(NSString *_Nonnull)message;\n\n+ (instancetype _Nullable)connectTo:(NSString *_Nonnull)address;\n+ (BOOL)parseV4Address:(NSString *_Nonnull)address into:(struct sockaddr_storage *_Nonnull)serverAddr;\n\n+ (void)multicastServe:(const char *_Nonnull)multicast port:(const char *_Nonnull)port;\n+ (NSString *_Nonnull)getMulticastService:(const char *_Nonnull)multicast\n                                     port:(const char *_Nonnull)port\n                                  message:(const char *_Nonnull)format;\n\n- (instancetype _Nonnull)initSocket:(int)socket;\n\n- (void)run;\n- (void)runInBackground;\n\n- (int)readInt;\n- (void * _Nullable)readPointer;\n- (NSData *_Nullable)readData;\n- (NSString *_Nullable)readString;\n- (BOOL)readBytes:(void * _Nonnull)buffer length:(size_t)length cmd:(SEL _Nonnull)cmd;\n\n- (BOOL)writeInt:(int)length;\n- (BOOL)writePointer:(void * _Nullable)pointer;\n- (BOOL)writeData:(NSData *_Nonnull)data;\n- (BOOL)writeString:(NSString *_Nonnull)string;\n- (BOOL)writeCommand:(int)command withString:(NSString *_Nullable)string;\n\n@end\n"
  },
  {
    "path": "Sources/HotReloadingGuts/include/UserDefaults.h",
    "content": "//\n//  UserDefaults.h\n//  InjectionIII\n//\n//  Created by Christoffer Winterkvist on 10/25/18.\n//  Copyright © 2018 John Holdsworth. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\nstatic NSString *const UserDefaultsTDDEnabled = @\"Enabled TDD\";\nstatic NSString *const UserDefaultsVaccineEnabled = @\"Enabled Vaccine\";\nstatic NSString *const UserDefaultsLastWatched = @\"lastWatched\";\nstatic NSString *const UserDefaultsLastProject = @\"lastProject\";\nstatic NSString *const UserDefaultsProjectFile = @\"projectFile\";\nstatic NSString *const UserDefaultsBookmarks = @\"persistentBookmarks\";\nstatic NSString *const UserDefaultsUpdateCheck = @\"nextUpdateCheck\";\nstatic NSString *const UserDefaultsOrderFront = @\"orderFront\";\nstatic NSString *const UserDefaultsFeedback = @\"feedback\";\nstatic NSString *const UserDefaultsLookup = @\"typeLookup\";\nstatic NSString *const UserDefaultsRemote = @\"appRemote\";\nstatic NSString *const UserDefaultsReplay = @\"replayInjections\";\nstatic NSString *const UserDefaultsUnlock = @\"deviceUnlock\";\nstatic NSString *const UserDefaultsFeed = @\"frontendFeed\";\n"
  },
  {
    "path": "Sources/injectiond/AppDelegate.swift",
    "content": "//\n//  AppDelegate.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/AppDelegate.swift#82 $\n//\n\nimport Cocoa\n#if SWIFT_PACKAGE\nimport injectiondGuts\nimport RemoteUI\n\n// nib compatability...\nimport WebKit\n@objc(WebView)\nclass WebView : WKWebView {}\n#endif\n\nlet XcodeBundleID = \"com.apple.dt.Xcode\"\nvar appDelegate: AppDelegate!\n\nenum InjectionState: String {\n    case ok = \"OK\"\n    case idle = \"Idle\"\n    case busy = \"Busy\"\n    case error = \"Error\"\n    case ready = \"Ready\"\n}\n\n@objc(AppDelegate)\nclass AppDelegate : NSObject, NSApplicationDelegate {\n\n    @IBOutlet var window: NSWindow!\n    @IBOutlet weak var enableWatcher: NSMenuItem!\n    @IBOutlet weak var traceItem: NSMenuItem!\n    @IBOutlet weak var traceInclude: NSTextField!\n    @IBOutlet weak var traceExclude: NSTextField!\n    @IBOutlet weak var traceFilters: NSWindow!\n    @IBOutlet weak var statusMenu: NSMenu!\n    @IBOutlet weak var startItem: NSMenuItem!\n    @IBOutlet weak var xprobeItem: NSMenuItem!\n    @IBOutlet weak var enabledTDDItem: NSMenuItem!\n    @IBOutlet weak var enableVaccineItem: NSMenuItem!\n    @IBOutlet weak var windowItem: NSMenuItem!\n    @IBOutlet weak var remoteItem: NSMenuItem!\n    @IBOutlet weak var updateItem: NSMenuItem!\n    @IBOutlet weak var frontItem: NSMenuItem!\n    @IBOutlet weak var feedbackItem: NSMenuItem!\n    @IBOutlet weak var lookupItem: NSMenuItem!\n    @IBOutlet weak var sponsorItem: NSMenuItem!\n    @IBOutlet var statusItem: NSStatusItem!\n\n    var watchedDirectories = Set<String>()\n    weak var lastConnection: InjectionServer?\n    var selectedProject: String?\n    let openProject = NSLocalizedString(\"Select Project Directory\",\n                                        tableName: \"Project Directory\",\n                                        comment: \"Project Directory\")\n\n    @objc let defaults = UserDefaults.standard\n    var defaultsMap: [NSMenuItem: String]!\n\n    lazy var isSandboxed =\n        ProcessInfo.processInfo.environment[\"APP_SANDBOX_CONTAINER_ID\"] != nil\n    var runningXcodeDevURL: URL? =\n        NSRunningApplication.runningApplications(\n            withBundleIdentifier: XcodeBundleID).first?\n            .bundleURL?.appendingPathComponent(\"Contents/Developer\")\n    var derivedLogs: String?\n\n    /// Bringing in InjectionNext  patching\n    static var ui: AppDelegate { return appDelegate }\n    static func alreadyWatching(_ projectRoot: String) -> String? {\n        return appDelegate.watchedDirectories.first { projectRoot.hasPrefix($0) }\n    }\n    @IBOutlet weak var deviceTesting: NSMenuItem?\n    @IBOutlet weak var selectXcodeItem: NSMenuItem?\n    @IBOutlet weak var patchCompilerItem: NSMenuItem?\n    @IBOutlet weak var librariesField: NSTextField!\n    var codeSigningID: String { selectedProject.flatMap {\n        defaults.string(forKey: $0) } ?? \"-\" }\n    func watch(path: String) {\n        watchedDirectories.insert(path)\n        lastConnection?.watchDirectory(path)\n    }\n    #if !SWIFT_PACKAGE\n    @IBAction func prepareXcode(_ sender: NSMenuItem) {\n        let open = NSOpenPanel()\n        open.prompt = \"Select Xcode to Patch\"\n        open.directoryURL = URL(fileURLWithPath: Defaults.xcodePath)\n        open.canChooseDirectories = false\n        open.canChooseFiles = true\n        if open.runModal() == .OK, let path = open.url?.path {\n            selectXcodeItem?.toolTip = path\n            Defaults.xcodeDefault = path\n            patchCompiler(sender)\n        }\n    }\n    #endif\n\n    @objc func applicationDidFinishLaunching(_ aNotification: Notification) {\n        // Insert code here to initialize your application\n        appDelegate = self\n\n        let statusBar = NSStatusBar.system\n        statusItem = statusBar.statusItem(withLength: statusBar.thickness)\n        statusItem.highlightMode = true\n        statusItem.menu = statusMenu\n        statusItem.isEnabled = true\n        statusItem.title = \"\"\n\n        if isSandboxed {\n//            sponsorItem.isHidden = true\n            updateItem.isHidden = true\n        } else if let platform = getenv(\"PLATFORM_NAME\"),\n           strcmp(platform, \"iphonesimulator\") == 0 {\n            DeviceServer.startServer(HOTRELOADING_PORT)\n        } else if let unlock = defaults.string(forKey: UserDefaultsUnlock) {\n            let deviceInform = \"deviceInform\"\n            var openPort = \"\"\n            if unlock == \"any\" {\n                if defaults.string(forKey: deviceInform) == nil {\n                    let alert: NSAlert = NSAlert()\n                    alert.messageText = \"Device Injection\"\n                    alert.informativeText = \"\"\"\n                        This release supports injection on a real device \\\n                        as well as in the simulator. In order to do this it \\\n                        needs to open a port to receive socket connections \\\n                        from a device which will provoke an OS warning if \\\n                        your Mac's firewall is enabled. Decline the prompt \\\n                        if you don't intend to use this feature.\n                        \"\"\"\n                    alert.alertStyle = .critical\n                    alert.addButton(withTitle: \"OK\")\n                    _ = alert.runModal()\n                    defaults.set(\"Informed\", forKey: deviceInform)\n                }\n                openPort = \"*\"\n                setenv(\"XPROBE_ANY\", \"1\", 1)\n                DeviceServer.multicastServe(HOTRELOADING_MULTICAST,\n                                            port: HOTRELOADING_PORT)\n            }\n            DeviceServer.startServer(openPort+HOTRELOADING_PORT)\n        }\n\n        #if !SWIFT_PACKAGE\n        InjectionServer.startServer(INJECTION_ADDRESS)\n        #endif\n\n        defaultsMap = [\n            frontItem: UserDefaultsOrderFront,\n            enabledTDDItem: UserDefaultsTDDEnabled,\n            enableVaccineItem: UserDefaultsVaccineEnabled,\n            feedbackItem: UserDefaultsFeedback,\n            lookupItem: UserDefaultsLookup,\n            remoteItem: UserDefaultsRemote\n        ]\n\n        for (menuItem, defaultsKey) in defaultsMap {\n            menuItem.state = defaults.bool(forKey: defaultsKey) ? .on : .off\n        }\n\n        #if SWIFT_PACKAGE\n        if remoteItem.state == .on {\n            remoteItem.state = .off\n            startRemote(remoteItem)\n        }\n        #else\n        if !isSandboxed && defaults.value(forKey: UserDefaultsFeed) != nil {\n            selectXcodeItem?.isHidden = false\n            selectXcodeItem?.toolTip = Defaults.xcodePath\n            selectXcodeItem?.state =\n                updatePatchUnpatch() == .patched ? .on : .off\n        }\n        #endif\n\n        setMenuIcon(.idle)\n        versionSpecific()\n    }\n\n    func versionSpecific() {\n        #if SWIFT_PACKAGE\n        let appName = \"Hot Reloading\"\n        statusMenu.item(withTitle: \"Open Project\")?.isHidden = true\n        var arguments = CommandLine.arguments.dropFirst()\n        let projectURL = URL(fileURLWithPath: arguments.removeFirst())\n        let projectRoot = projectURL.deletingLastPathComponent()\n        AppDelegate.ensureInterposable(project: projectURL.path)\n        NSDocumentController.shared.noteNewRecentDocumentURL(projectRoot)\n        derivedLogs = arguments.removeFirst()\n\n        selectedProject = projectURL.path\n        appDelegate.watchedDirectories = [projectRoot.path]\n        for dir in arguments where !dir.hasPrefix(projectRoot.path) {\n            appDelegate.watchedDirectories.insert(dir)\n        }\n        #else\n        let appName = \"InjectionIII\"\n        DDHotKeyCenter.shared()?\n            .registerHotKey(withKeyCode: UInt16(kVK_ANSI_Equal),\n               modifierFlags: NSEvent.ModifierFlags.control.rawValue,\n               target:self, action:#selector(autoInject(_:)), object:nil)\n\n        NSApp.servicesProvider = self\n        if let projectFile = defaults\n            .string(forKey: UserDefaultsProjectFile) {\n            // Received project file from command line option.\n            _ = self.application(NSApp, openFile:\n                URL(fileURLWithPath: projectFile).deletingLastPathComponent().path)\n        } else if let lastWatched = defaults.string(forKey: UserDefaultsLastWatched) {\n            _ = self.application(NSApp, openFile: lastWatched)\n        } else {\n            NSUpdateDynamicServices()\n        }\n\n        let nextUpdateCheck = defaults.double(forKey: UserDefaultsUpdateCheck)\n        if !isSandboxed && nextUpdateCheck != 0.0 {\n            updateItem.state = .on\n            if Date.timeIntervalSinceReferenceDate > nextUpdateCheck {\n                self.updateCheck(nil)\n            }\n        }\n        #endif\n        statusItem.title = appName\n        if let quit = statusMenu.item(at: statusMenu.items.count-1) {\n            quit.title = \"Quit \"+appName\n            #if !SWIFT_PACKAGE\n            if let build = Bundle.main\n                .infoDictionary?[kCFBundleVersionKey as String] {\n                quit.toolTip = \"Quit (build #\\(build))\"\n            }\n            #endif\n        }\n    }\n\n    func application(_ theApplication: NSApplication, openFile filename: String) -> Bool {\n        #if SWIFT_PACKAGE\n        return false\n        #else\n        guard filename != Bundle.main.bundlePath,\n            let url = resolve(path: filename),\n            let fileList = try? FileManager.default\n               .contentsOfDirectory(atPath: url.path) else {\n            return false\n        }\n\n        if url.pathExtension == \"xcworkspace\" ||\n            url.pathExtension == \"xcodeproj\" {\n            let alert: NSAlert = NSAlert()\n            alert.messageText = \"InjectionIII\"\n            alert.informativeText = \"\"\"\n                Please select the project directory to watch \\\n                for file changes under, not the project file.\n                \"\"\"\n            alert.alertStyle = NSAlert.Style.warning\n            alert.addButton(withTitle: \"Sorry\")\n            _ = alert.runModal()\n            return false\n        }\n\n        let projectFiles = SwiftEval.projects(in: fileList)\n\n        selectedProject = nil\n        if url.path.hasSuffix(\".swiftpm\") {\n            selectedProject = url.path\n            let pkg = url.appendingPathComponent(\"Package.swift\")\n            if let manifest = try? String(contentsOf: pkg),\n                !manifest.contains(\"-interposable\") {\n                var modified = manifest\n                modified[#\"\"\"\n                    (\n                            \\)\n                        ]\n                    \\)\n                    )\\Z\n                    \"\"\"#] = \"\"\"\n                    ,\n                                linkerSettings: [\n                                    .unsafeFlags([\"-Xlinker\", \"-interposable\"],\n                                                 .when(configuration: .debug))\n                                ]$1\n                    \"\"\"\n                if modified != manifest {\n                    do {\n                        try modified.write(to: pkg, atomically: true, encoding: .utf8)\n                        let alert: NSAlert = NSAlert()\n                        alert.messageText = \"InjectionIII\"\n                        alert.informativeText = \"\"\"\n                            InjectionIII has patched Package.swift to include the -interposable linker flag. Use Menu item \"Prepare Project\" to complete conversion.\n                            \"\"\"\n                        alert.alertStyle = NSAlert.Style.warning\n                        alert.addButton(withTitle: \"OK\")\n                        _ = alert.runModal()\n                    } catch {\n                    }\n                }\n            }\n        } else if projectFiles == nil || projectFiles!.count > 1 {\n            for lastProjectFile in [UserDefaultsProjectFile,\n                                    UserDefaultsLastProject]\n                .compactMap({ defaults.string(forKey: $0) }) {\n                for project in projectFiles ?? [] {\n                    if selectedProject == nil,\n                        url.appendingPathComponent(project)\n                            .path == lastProjectFile {\n                        selectedProject = lastProjectFile\n                    }\n                }\n            }\n            if selectedProject == nil {\n                let open = NSOpenPanel()\n                open.prompt = \"Select Project File\"\n                open.directoryURL = url\n                open.canChooseDirectories = false\n                open.canChooseFiles = true\n                // open.showsHiddenFiles = TRUE;\n                if open.runModal() == .OK,\n                    let url = open.url {\n                    selectedProject = url.path\n                }\n            }\n        } else if projectFiles != nil {\n            selectedProject = url\n                .appendingPathComponent(projectFiles![0]).path\n        }\n\n        guard let projectFile = selectedProject else {\n            let alert: NSAlert = NSAlert()\n            alert.messageText = \"InjectionIII\"\n            alert.informativeText = \"Please select a directory with either a .xcworkspace or .xcodeproj file, below which, are the files you wish to inject.\"\n            alert.alertStyle = NSAlert.Style.warning\n            alert.addButton(withTitle: \"OK\")\n            _ = alert.runModal()\n            return false\n        }\n\n        watchedDirectories.removeAll()\n        watchedDirectories.insert(url.path)\n        if let alsoWatch = defaults.string(forKey: \"addDirectory\"),\n            let resolved = resolve(path: alsoWatch) {\n            watchedDirectories.insert(resolved.path)\n        }\n        lastConnection?.setProject(projectFile)\n//            AppDelegate.ensureInterposable(project: selectedProject!)\n        NSDocumentController.shared.noteNewRecentDocumentURL(url)\n        statusItem.menu?.item(withTitle: \"Open Recent\")?.toolTip = url.path\n//            let projectName = URL(fileURLWithPath: projectFile)\n//                .deletingPathExtension().lastPathComponent\n//            traceInclude.stringValue = projectName\n//            updateTraceInclude(nil)\n        defaults.set(projectFile, forKey: UserDefaultsLastProject)\n        defaults.set(url.path, forKey: UserDefaultsLastWatched)\n        return true\n        #endif\n    }\n\n    func persist(url: URL) {\n        if !isSandboxed { return }\n        var bookmarks = defaults.value(forKey: UserDefaultsBookmarks)\n            as? [String : Data] ?? [String: Data]()\n        do {\n            bookmarks[url.path] =\n                try url.bookmarkData(options: [.withSecurityScope,\n                                               .securityScopeAllowOnlyReadAccess],\n                                     includingResourceValuesForKeys: [],\n                                     relativeTo: nil)\n            defaults.set(bookmarks, forKey: UserDefaultsBookmarks)\n        } catch {\n            _ = InjectionServer.error(\"Bookmarking failed for \\(url), \\(error)\")\n        }\n    }\n\n    func resolve(path: String) -> URL? {\n        var isStale: Bool = false\n        if !isSandboxed, FileManager.default.fileExists(atPath: path) {\n            return URL(fileURLWithPath: path)\n        } else if let bookmarks =\n            defaults.value(forKey: UserDefaultsBookmarks) as? [String : Data],\n            let bookmark = bookmarks[path],\n            let resolved = try? URL(resolvingBookmarkData: bookmark,\n                           options: .withSecurityScope,\n                           relativeTo: nil,\n                           bookmarkDataIsStale: &isStale), !isStale {\n            _ = resolved.startAccessingSecurityScopedResource()\n            return resolved\n        } else {\n            let open = NSOpenPanel()\n            open.prompt = openProject\n            if path != \"\" {\n                open.directoryURL = URL(fileURLWithPath: path)\n            }\n            open.canChooseDirectories = true\n            open.canChooseFiles = true\n            // open.showsHiddenFiles = TRUE;\n            if open.runModal() == .OK,\n                let url = open.url {\n                persist(url: url)\n                return url\n            }\n        }\n\n        return nil\n    }\n\n    func setMenuIcon(_ state: InjectionState) {\n        DispatchQueue.main.async {\n            let tiffName = \"Injection\"+state.rawValue\n            if let path = Bundle.main.path(forResource: tiffName, ofType: \"tif\"),\n                let image = NSImage(contentsOfFile: path) {\n    //            image.template = TRUE;\n                self.statusItem.image = image\n                self.statusItem.alternateImage = self.statusItem.image\n                let appRunning = tiffName != \"InjectionIdle\"\n                self.startItem.isEnabled = appRunning\n                self.xprobeItem.isEnabled = appRunning\n                for item in self.traceItem.submenu!.items {\n                    if item.tag == 0 {\n                        item.isEnabled = appRunning\n                        if !appRunning {\n                            item.state = .off\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @IBAction func openProject(_ sender: Any) {\n        _ = application(NSApp, openFile: \"\")\n    }\n\n    @IBAction func addDirectory(_ sender: Any) {\n        let open = NSOpenPanel()\n        open.prompt = openProject\n        open.allowsMultipleSelection = true\n        open.canChooseDirectories = true\n        open.canChooseFiles = false\n        if open.runModal() == .OK {\n            for url in open.urls {\n                watch(path: url.path)\n                persist(url: url)\n            }\n        }\n    }\n\n    func setFrameworks(_ frameworks: String, menuTitle: String) {\n        DispatchQueue.main.async {\n            guard let frameworksMenu = self.traceItem.submenu?\n                    .item(withTitle: menuTitle)?.submenu else { return }\n            frameworksMenu.removeAllItems()\n            for framework in frameworks\n                .components(separatedBy: FRAMEWORK_DELIMITER).sorted()\n                where framework != \"\" {\n                frameworksMenu.addItem(withTitle: framework, action:\n                    #selector(self.traceFramework(_:)), keyEquivalent: \"\")\n                    .target = self\n            }\n        }\n    }\n\n    @objc func traceFramework(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.traceFramework, with: sender.title)\n    }\n\n    @IBAction func toggleTDD(_ sender: NSMenuItem) {\n        toggleState(sender)\n    }\n\n    @IBAction func toggleVaccine(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.vaccineSettingChanged, with:vaccineConfiguration())\n    }\n\n    @IBAction func toggleFeedback(_ sender: NSMenuItem?) {\n        sender.flatMap { toggleState($0) }\n        lastConnection?.sendCommand(.feedback,\n                                    with: feedbackItem.state == .on ? \"1\" : \"0\")\n    }\n\n    @IBAction func toggleLookup(_ sender: NSMenuItem?) {\n        sender.flatMap { toggleState($0) }\n        lastConnection?.sendCommand(.lookup,\n                                    with: lookupItem.state == .on ? \"1\" : \"0\")\n    }\n\n    @IBAction func startRemote(_ sender: NSMenuItem) {\n        #if SWIFT_PACKAGE\n        remoteItem.state = .off\n        toggleState(remoteItem)\n        RMWindowController.startServer(sender)\n        #endif\n    }\n\n    @IBAction func stopRemote(_ sender: NSMenuItem) {\n        #if SWIFT_PACKAGE\n        remoteItem.state = .on\n        toggleState(remoteItem)\n        RMWindowController.stopServer()\n        #endif\n    }\n\n    @IBAction func traceApp(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(sender.state == .on ?\n            .trace : .untrace, with: nil)\n    }\n\n    @IBAction func traceUIApp(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.traceUI, with: nil)\n    }\n\n    @IBAction func traceUIKit(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.traceUIKit, with: nil)\n    }\n\n    @IBAction func traceSwiftUI(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.traceSwiftUI, with: nil)\n    }\n\n    @IBAction func profileSwiftUI(_ sender: NSMenuItem) {\n        toggleState(sender)\n        lastConnection?.sendCommand(.profileUI, with: nil)\n    }\n\n    @IBAction func traceStats(_ sender: NSMenuItem) {\n        lastConnection?.sendCommand(.stats, with: nil)\n    }\n\n    @IBAction func remmoveTraces(_ sender: NSMenuItem?) {\n        lastConnection?.sendCommand(.uninterpose, with: nil)\n    }\n\n    @IBAction func showTraceFilters(_ sender: NSMenuItem?) {\n        NSApplication.shared.activate(ignoringOtherApps: true)\n        traceFilters.makeKeyAndOrderFront(sender)\n    }\n\n    @IBAction func updateTraceInclude(_ sender: NSButton?) {\n        guard traceInclude.stringValue != \"\" || sender != nil else { return }\n        update(filter: sender == nil ? .quietInclude : .include,\n               textField: traceInclude)\n    }\n\n    @IBAction func updateTraceExclude(_ sender: NSButton?) {\n        guard traceExclude.stringValue != \"\" || sender != nil else { return }\n        update(filter: .exclude, textField: traceExclude)\n    }\n\n    func update(filter: InjectionCommand, textField: NSTextField) {\n        let regex = textField.stringValue\n        do {\n            if regex != \"\" {\n                _ = try NSRegularExpression(pattern: regex, options: [])\n            }\n            lastConnection?.sendCommand(filter, with: regex)\n        } catch {\n            let alert = NSAlert(error: error)\n            alert.informativeText = \"Invalid regular expression syntax '\\(regex)' for filter. Characters [](){}|?*+\\\\ and . have special meanings. Type: man re_syntax, in the terminal.\"\n            alert.runModal()\n            textField.becomeFirstResponder()\n            showTraceFilters(nil)\n        }\n    }\n\n    func vaccineConfiguration() -> String {\n        let vaccineSetting = UserDefaults.standard.bool(forKey: UserDefaultsVaccineEnabled)\n        let dictionary = [UserDefaultsVaccineEnabled: vaccineSetting]\n        let jsonData = try! JSONSerialization\n            .data(withJSONObject: dictionary, options:[])\n        let configuration = String(data: jsonData, encoding: .utf8)!\n        return configuration\n    }\n\n    @IBAction func toggleState(_ sender: NSMenuItem) {\n        sender.state = sender.state == .on ? .off : .on\n        if let defaultsKey = defaultsMap[sender] {\n            defaults.set(sender.state, forKey: defaultsKey)\n        }\n    }\n\n    @IBAction func autoInject(_ sender: NSMenuItem) {\n        lastConnection?.injectPending()\n//    #if false\n//        NSError *error = nil;\n//        // Install helper tool\n//        if ([HelperInstaller isInstalled] == NO) {\n//    #pragma clang diagnostic push\n//    #pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n//            if ([[NSAlert alertWithMessageText:@\"Injection Helper\"\n//                                 defaultButton:@\"OK\" alternateButton:@\"Cancel\" otherButton:nil\n//                     informativeTextWithFormat:@\"InjectionIII needs to install a privileged helper to be able to inject code into \"\n//                  \"an app running in the iOS simulator. This is the standard macOS mechanism.\\n\"\n//                  \"You can remove the helper at any time by deleting:\\n\"\n//                  \"/Library/PrivilegedHelperTools/com.johnholdsworth.InjectorationIII.Helper.\\n\"\n//                  \"If you'd rather not authorize, patch the app instead.\"] runModal] == NSAlertAlternateReturn)\n//                return;\n//    #pragma clang diagnostic pop\n//            if ([HelperInstaller install:&error] == NO) {\n//                NSLog(@\"Couldn't install Smuggler Helper (domain: %@ code: %d)\", error.domain, (int)error.code);\n//                [[NSAlert alertWithError:error] runModal];\n//                return;\n//            }\n//        }\n//\n//        // Inject Simulator process\n//        NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@\"iOSInjection\" ofType:@\"bundle\"];\n//        if ([HelperProxy inject:bundlePath error:&error] == FALSE) {\n//            NSLog(@\"Couldn't inject Simulator (domain: %@ code: %d)\", error.domain, (int)error.code);\n//            [[NSAlert alertWithError:error] runModal];\n//        }\n//    #endif\n    }\n\n    @IBAction func help(_ sender: Any) {\n        _ = NSWorkspace.shared.open(URL(string:\n            \"https://github.com/johnno1962/InjectionIII\")!)\n    }\n\n    @IBAction func sponsor(_ sender: Any) {\n        _ = NSWorkspace.shared.open(URL(string:\n            \"https://github.com/sponsors/johnno1962\")!)\n    }\n\n    @IBAction func book(_ sender: Any) {\n        _ = NSWorkspace.shared.open(URL(string:\n            \"https://books.apple.com/book/id1551005489\")!)\n    }\n\n    @objc\n    public func applicationWillTerminate(aNotification: NSNotification) {\n            // Insert code here to tear down your application\n        #if !SWIFT_PACKAGE\n        DDHotKeyCenter.shared()\n            .unregisterHotKey(withKeyCode: UInt16(kVK_ANSI_Equal),\n             modifierFlags: NSEvent.ModifierFlags.control.rawValue)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/injectiond/DeviceServer.swift",
    "content": "//\n//  DeviceServer.swift\n//  InjectionIII\n//  \n//  Created by John Holdsworth on 13/01/2022.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/DeviceServer.swift#35 $\n//\n\nimport Foundation\n#if SWIFT_PACKAGE\nimport HotReloadingGuts\n#endif\n\nclass DeviceServer: InjectionServer {\n\n    var scratchPointer: UnsafeMutableRawPointer?\n    var lastSource: String?\n    var loadFailed = false\n\n    #if !SWIFT_PACKAGE\n    override func validateConnection() -> Bool {\n        switch readInt() {\n        case INJECTION_SALT:\n            return readString() == INJECTION_KEY\n        case HOTRELOADING_SALT:\n            return readString()?.hasPrefix(NSHomeDirectory()) == true\n        default:\n            return false\n        }\n    }\n    #endif\n\n    override func process(response: InjectionResponse, executable: String) {\n        switch response {\n        case .scratchPointer:\n            scratchPointer = readPointer()\n            builder.tmpDir = NSTemporaryDirectory()\n            appDelegate.setMenuIcon(scratchPointer != nil ? .ok : .error)\n        #if DEBUG\n        case .testInjection:\n            if let file = readString(), let source = readString() {\n                do {\n                    if file.hasPrefix(\"/Users/johnholdsworth/Developer/\") {\n                        try source.write(toFile: file, atomically: true, encoding: .utf8)\n                    }\n                } catch {\n                    log(\"Error writing test source file: \\(error)\")\n                }\n            }\n        #endif\n        case .error:\n            compileQueue.sync {\n                if !loadFailed, let source = lastSource {\n                    loadFailed = true\n                    builder.updateLongTermCache(remove: source)\n                    recompileAndInject(source: source)\n                }\n            }\n            fallthrough\n        default:\n            super.process(response: response, executable: executable)\n        }\n    }\n\n    override func recompileAndInject(source: String) {\n        appDelegate.setMenuIcon(.busy)\n        lastSource = source\n        if let slide = self.scratchPointer {\n            if let unlock = UserDefaults.standard\n                .string(forKey: UserDefaultsUnlock) {\n                writeCommand(InjectionCommand.pseudoUnlock.rawValue, with: unlock)\n            }\n            compileQueue.async {\n                self.builder.linkerOptions =\n                    \" -fuse-ld=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic\" +\n                    \" -Xlinker -image_base -Xlinker 0x\" +\n                    String(Int(bitPattern: slide), radix: 16)\n                do {\n                    let dylib = try self.prepare(source: source)\n                    if source[#\"\\.mm?$\"#], // class references in Objective-C\n                       var sourceText = try? String(contentsOfFile: source) {\n                        sourceText[#\"//.*|/\\*[^*]+\\*/\"#] = \"\" // zap comments\n                        self.objcClassRefs.removeAllObjects()\n                        var seen = Set<String>()\n                        for messagedClass: String\n                                in sourceText[#\"\\[([A-Z]\\w+) \"#] {\n                            if seen.insert(messagedClass).inserted {\n                                self.objcClassRefs.add(messagedClass)\n                            }\n                        }\n                    }\n                    if let objcClasses = self.objcClassRefs as? [String],\n                       let descriptors = self.descriptorRefs as? [String],\n                       let data = try? NSData(contentsOfFile: \"\\(dylib).dylib\") as Data {\n                        commandQueue.async {\n                            self.writeCommand(InjectionCommand.objcClassRefs.rawValue,\n                                              with: objcClasses.joined(separator: \",\"))\n                            self.writeCommand(InjectionCommand.descriptorRefs.rawValue,\n                                              with: descriptors.joined(separator: \",\"))\n                            self.writeCommand(InjectionCommand.pseudoInject.rawValue,\n                                              with: source)\n                            self.writePointer(slide)\n                            self.write(data as Data)\n                        }\n                        return\n                    }\n                } catch {\n                    NSLog(\"\\(error)\")\n                }\n            }\n        } else { // You can load a dylib on device after all...\n            super.recompileAndInject(source: source)\n        }\n    }\n\n    override func inject(dylib: String) {\n        if isLocalClient {\n            return super.inject(dylib: dylib)\n        }\n        if let data = NSData(contentsOfFile: \"\\(dylib).dylib\") {\n            commandQueue.sync {\n                write(InjectionCommand.copy.rawValue)\n                write(data as Data)\n                appDelegate.setMenuIcon(.ok)\n            }\n        } else {\n            sendCommand(.log, with: \"\\(APP_PREFIX)Error reading \\(dylib).dylib\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/injectiond/Experimental.swift",
    "content": "//\n//  Experimental.swift\n//  InjectionIII\n//\n//  Created by User on 20/10/2020.\n//  Copyright © 2020 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/Experimental.swift#39 $\n//\n\nimport Cocoa\n#if SWIFT_PACKAGE\nimport HotReloadingGuts\nimport injectiondGuts\nimport SwiftRegex\nimport XprobeUI\n#endif\n\nextension AppDelegate {\n\n    @IBAction func runXprobe(_ sender: NSMenuItem) {\n        #if SWIFT_PACKAGE\n        if xprobePlugin == nil {\n            xprobePlugin = XprobePluginMenuController()\n            xprobePlugin.applicationDidFinishLaunching(\n                Notification(name: Notification.Name(rawValue: \"\")))\n            xprobePlugin.injectionPlugin = unsafeBitCast(self, to: AnyClass.self)\n        }\n        lastConnection?.sendCommand(.xprobe, with: \"\")\n        windowItem.isHidden = false\n        #endif\n    }\n\n    @objc func evalCode(_ swift: String) {\n        lastConnection?.sendCommand(.eval, with:swift)\n    }\n\n    @IBAction func callOrder(_ sender: NSMenuItem) {\n        lastConnection?.sendCommand(.callOrder, with: nil)\n    }\n\n    @IBAction func fileOrder(_ sender: NSMenuItem) {\n        lastConnection?.sendCommand(.fileOrder, with: nil)\n    }\n\n    @IBAction func fileReorder(_ sender: NSMenuItem) {\n        lastConnection?.sendCommand(.fileReorder, with: nil)\n    }\n\n    @IBAction func objectCounts(_ sender: NSMenuItem) {\n        lastConnection?.sendCommand(.counts, with: nil)\n    }\n\n    func fileReorder(signatures: [String]) {\n        var projectEncoding: String.Encoding = .utf8\n        let projectURL = selectedProject.flatMap {\n            URL(fileURLWithPath: $0\n                .replacingOccurrences(of: \".xcworkspace\", with: \".xcodeproj\"))\n            }\n        guard let pbxprojURL = projectURL?\n                .appendingPathComponent(\"project.pbxproj\"),\n            let projectSource = try? String(contentsOf: pbxprojURL,\n                                            usedEncoding: &projectEncoding)\n        else {\n            lastConnection?.sendCommand(.log, with:\n                \"\\(APP_PREFIX)Could not load project file \\(projectURL?.path ?? \"unknown\").\")\n            return\n        }\n\n        var orders = [\"AppDelegate.swift\": 0]\n        var order = 1\n        SwiftEval.uniqueTypeNames(signatures: signatures) { typeName in\n            orders[typeName+\".swift\"] = order\n            order += 1\n        }\n\n        var newProjectSource = projectSource\n        // For each PBXSourcesBuildPhase in project file\n        newProjectSource[#\"\"\"\n            ^\\s+isa = PBXSourcesBuildPhase;\n            \\s+buildActionMask = \\d+;\n            \\s+files = \\(\n            ((?:[^\\n]+\\n)*?)\\#\n            \\s+\\);\n\n            \"\"\"#.anchorsMatchLines, group: 1] = {\n                (sources: String, stop) -> String in\n                // reorder the lines for each file in the PBXSourcesBuildPhase\n                // to bring those traced first to the front of the app binary.\n                // This localises the startup code in as few pages as possible.\n                return (sources[#\"(\\s+\\S+ /\\* (\\S+) in Sources \\*/,\\n)\"#]\n                            as [(line: String, file: String)]).sorted(by: {\n                    orders[$0.file] ?? order < orders[$1.file] ?? order\n                }).map { $0.line }.joined()\n            }\n\n        DispatchQueue.main.sync {\n            let project = projectURL!.lastPathComponent\n            let backup = pbxprojURL.path+\".preorder\"\n            let alert = NSAlert()\n            alert.messageText = \"About to reorder '\\(project)'\"\n            alert.informativeText = \"This experimental feature will modify the order of source files in memory to reduce paging on startup. There will be a backup of the project file before re-ordering at: \\(backup)\"\n            alert.addButton(withTitle: \"Cancel\")\n            alert.addButton(withTitle: \"Go ahead\")\n            switch alert.runModal() {\n            case .alertSecondButtonReturn:\n                do {\n                    if !FileManager.default.fileExists(atPath: backup) {\n                        try projectSource.write(toFile: backup, atomically: true,\n                                                encoding: projectEncoding)\n                    }\n                    try newProjectSource.write(to: pbxprojURL, atomically: true,\n                                               encoding: projectEncoding)\n                } catch {\n                    NSAlert(error: error).runModal()\n                }\n            default:\n                break\n            }\n        }\n    }\n\n    /// Entry point for \"Injection Goto\" service\n    /// - Parameters:\n    ///   - pboard: NSPasteboard containing selected type [+method) name\n    ///   - userData: N/A\n    ///   - errorPtr: NSString describing error on error\n    @objc func injectionGoto(_ pboard: NSPasteboard, userData: NSString,\n                             error errorPtr: UnsafeMutablePointer<NSString>) {\n        guard pboard.canReadObject(forClasses: [NSString.self], options:nil),\n            let target = pboard.string(forType: .string) else { return }\n\n        let parts = target.components(separatedBy: \".\")\n                        .filter { !$0.hasSuffix(\"init\") }\n        let builder = SwiftEval()\n        builder.projectFile = selectedProject\n\n        guard parts.count > 0, let (_, logsDir) =\n            try? builder.determineEnvironment(classNameOrFile: \"\") else {\n            errorPtr.pointee = \"\\(APP_PREFIX)Injection Goto service not availble.\" as NSString\n            lastConnection?.sendCommand(.log, with: errorPtr.pointee as String)\n            return\n        }\n\n        var className: String!, sourceFile: String?\n        let tmpDir = NSTemporaryDirectory()\n\n        for part in parts {\n            let subParts = part.components(separatedBy: \" \")\n            className = subParts[0]\n            if let (_, foundSourceFile) =\n                try? builder.findCompileCommand(logsDir: logsDir,\n                        classNameOrFile: className, tmpfile: tmpDir+\"/eval101\") {\n                sourceFile = foundSourceFile\n                className = subParts.count > 1 ? subParts.last : parts.last\n                break\n            }\n        }\n\n        className = className.replacingOccurrences(of: #\"\\((\\S+).*\"#,\n                                                   with: \"$1\",\n                                                   options: .regularExpression)\n\n        guard sourceFile != nil,\n            let sourceText = try? NSString(contentsOfFile: sourceFile!,\n                                           encoding: String.Encoding.utf8.rawValue),\n            let finder = try? NSRegularExpression(pattern:\n                #\"(?:\\b(?:var|func|struct|class|enum)\\s+|^[+-]\\s*(?:\\([^)]*\\)\\s*)?)(\\#(className!))\\b\"#,\n                options: [.anchorsMatchLines]) else {\n            errorPtr.pointee = \"\"\"\n                \\(APP_PREFIX)Unable to find source file for type '\\(className!)' \\\n                using build logs.\\n\\(APP_PREFIX)Do you have the right project selected? \\\n                Try with a clean build.\n                \"\"\" as NSString\n            lastConnection?.sendCommand(.log, with: errorPtr.pointee as String)\n            return\n        }\n\n        let match = finder.firstMatch(in: sourceText as String, options: [],\n                                      range: NSMakeRange(0, sourceText.length))\n\n        DispatchQueue.main.async {\n            if let xCode = SBApplication(bundleIdentifier: XcodeBundleID),\n//                xCode.activeWorkspaceDocument.path != nil,\n                let doc = xCode.open(sourceFile!) as? SBObject,\n                doc.selectedCharacterRange != nil,\n                let range = match?.range(at: 1) {\n                doc.selectedCharacterRange =\n                    [NSNumber(value: range.location+1),\n                     NSNumber(value: range.location+range.length)]\n            } else {\n                var numberOfLine = 0, index = 0\n\n                if let range = match?.range(at: 1) {\n                    while index < range.location {\n                        index = NSMaxRange(sourceText\n                                    .lineRange(for: NSMakeRange(index, 0)))\n                        numberOfLine += 1\n                    }\n                }\n\n                guard numberOfLine != 0 else { return }\n\n                var xed = \"/usr/bin/xed\"\n                if let xcodeURL = self.runningXcodeDevURL {\n                    xed = xcodeURL\n                        .appendingPathComponent(\"usr/bin/xed\").path\n                }\n\n                let script = tmpDir+\"/injection_goto.sh\"\n                do {\n                    try \"\\\"\\(xed)\\\" --line \\(numberOfLine) \\\"\\(sourceFile!)\\\"\"\n                        .write(toFile: script, atomically: false, encoding: .utf8)\n                    chmod(script, 0o700)\n\n                    let task = Process()\n                    task.launchPath = \"/usr/bin/open\"\n                    task.arguments = [\"-b\", \"com.apple.Terminal\", script]\n                    task.launch()\n                    task.waitUntilExit()\n                } catch {\n                    errorPtr.pointee = \"\\(APP_PREFIX)Failed to write \\(script): \\(error)\" as NSString\n                    NSLog(\"\\(errorPtr.pointee)\")\n                }\n            }\n        }\n    }\n\n    static func ensureInterposable(project: String) {\n        var projectEncoding: String.Encoding = .utf8\n        let projectURL = URL(fileURLWithPath: project)\n        let pbxprojURL = projectURL.appendingPathComponent(\"project.pbxproj\")\n        if let projectSource = try? String(contentsOf: pbxprojURL,\n                                           usedEncoding: &projectEncoding),\n           !projectSource.contains(\"-interposable\") {\n            var newProjectSource = projectSource\n            // For each PBXSourcesBuildPhase in project file...\n            // Make sure \"Other linker Flags\" includes -interposable\n            newProjectSource[#\"\"\"\n                /\\* Debug \\*/ = \\{\n                \\s+isa = XCBuildConfiguration;\n                (?:.*\\n)*?(\\s+)buildSettings = \\{\n                ((?:.*\\n)*?\\1\\};)\n                \"\"\"#, group: 2] = \"\"\"\n                                    OTHER_LDFLAGS = (\n                                        \"-Xlinker\",\n                                        \"-interposable\",\n                                    );\n                                    ENABLE_BITCODE = NO;\n                    $2\n                    \"\"\"\n\n            if newProjectSource != projectSource {\n                let backup = pbxprojURL.path+\".prepatch\"\n                if !FileManager.default.fileExists(atPath: backup) {\n                    try? projectSource.write(toFile: backup, atomically: true,\n                                            encoding: projectEncoding)\n                }\n                do {\n                    let alert = NSAlert()\n                    alert.messageText = \"injectiond\"\n                    alert.informativeText = \"\"\"\n                        \\(APP_NAME) can patch your project slightly to add the \\\n                        required -Xlinker -interposable \\\"Other Linker Flags\\\". \\\n                        Restart the app to have these changes take effect. \\\n                        A backup has been saved at: \\(backup)\n                        \"\"\"\n                    alert.addButton(withTitle: \"Go ahead\")\n                    alert.addButton(withTitle: \"Cancel\")\n                    if alert.runModal() == .alertFirstButtonReturn {\n                        try newProjectSource.write(to: pbxprojURL, atomically: true,\n                                                   encoding: projectEncoding)\n                    }\n                } catch {\n                    NSLog(\"Could not patch project \\(pbxprojURL): \\(error)\")\n                    let alert = NSAlert()\n                    alert.messageText = \"Could not process project file \\(projectURL): \\(error)\"\n                    _ = alert.runModal()\n                }\n            }\n        }\n    }\n\n    @IBAction func prepareProject(_ sender: NSMenuItem) {\n        guard let selectedProject = selectedProject else {\n            let alert = NSAlert()\n            alert.messageText = \"Please select a project directory.\"\n            _ = alert.runModal()\n            return\n        }\n\n        Self.ensureInterposable(project: selectedProject)\n\n        for directory in watchedDirectories {\n            prepareSwiftUI(projectRoot: URL(fileURLWithPath: directory))\n        }\n    }\n\n    func prepareSwiftUI(projectRoot: URL) {\n        do {\n            guard let enumerator = FileManager.default\n                    .enumerator(atPath: projectRoot.path) else {\n                return\n            }\n            let alert = NSAlert()\n            alert.messageText = \"About to patch SwiftUI files in the source directory: \\(projectRoot.path) for injection.\"\n            alert.addButton(withTitle: \"Go ahead\")\n            alert.addButton(withTitle: \"Cancel\")\n            switch alert.runModal() {\n            case .alertSecondButtonReturn:\n                return\n            default:\n                break\n            }\n\n            for file in enumerator {\n                guard let file = file as? String, file.hasSuffix(\".swift\"),\n                      !file.hasPrefix(\"Packages\") else {\n                    continue\n                }\n                let fileURL = projectRoot.appendingPathComponent(file)\n                guard let original = try? String(contentsOf: fileURL) else {\n                    continue\n                }\n\n                var patched = original\n                patched[#\"\"\"\n                    ^((\\s+)(public )?(var body:|func body\\([^)]*\\) -\\>) some View \\{\\n\\#\n                    (\\2(?!    (if|switch|ForEach) )\\s+(?!\\.enableInjection)\\S.*\\n|\\s*\\n)+)(?<!#endif\\n)\\2\\}\\n\n                    \"\"\"#.anchorsMatchLines] = \"\"\"\n                    $1$2    .enableInjection()\n                    $2}\n\n                    $2#if DEBUG\n                    $2@ObserveInjection var forceRedraw\n                    $2#endif\n\n                    \"\"\"\n\n                if (patched.contains(\"class AppDelegate\") ||\n                    patched.contains(\"@main\")) &&\n                    !patched.contains(\"InjectionObserver\") {\n                    #if SWIFT_PACKAGE\n                    let loadInjection = \"\"\"\n                            // HotReloading loads itself.\n                        \"\"\"\n                    #else\n                    let loadInjection = #\"\"\"\n                            guard objc_getClass(\"InjectionClient\") == nil else {\n                                return\n                            }\n                            #if os(macOS) || targetEnvironment(macCatalyst)\n                            let bundleName = \"macOSInjection.bundle\"\n                            #elseif os(tvOS)\n                            let bundleName = \"tvOSInjection.bundle\"\n                            #elseif os(visionOS)\n                            let bundleName = \"xrOSInjection.bundle\"\n                            #elseif targetEnvironment(simulator)\n                            let bundleName = \"iOSInjection.bundle\"\n                            #else\n                            let bundleName = \"maciOSInjection.bundle\"\n                            #endif\n                            let bundlePath = \"/Applications/InjectionIII.app/Contents/Resources/\"+bundleName\n                            guard let bundle = Bundle(path: bundlePath), bundle.load() else {\n                                return print(\"\"\"\n                                    ⚠️ Could not load injection bundle from \\(bundlePath). \\\n                                    Have you downloaded the InjectionIII.app from either \\\n                                    https://github.com/johnno1962/InjectionIII/releases \\\n                                    or the Mac App Store?\n                                    \"\"\")\n                            }\n                    \"\"\"#\n                    #endif\n\n                    if !patched.contains(\"import SwiftUI\") {\n                        patched += \"\\nimport SwiftUI\\n\"\n                    }\n\n                    patched += \"\"\"\n\n                        #if canImport(HotSwiftUI)\n                        @_exported import HotSwiftUI\n                        #elseif canImport(Inject)\n                        @_exported import Inject\n                        #else\n                        // This code can be found in the Swift package:\n                        // https://github.com/johnno1962/HotSwiftUI\n\n                        #if DEBUG\n                        import Combine\n\n                        private var loadInjectionOnce: () = {\n                        \\(loadInjection)\n                        }()\n\n                        public let injectionObserver = InjectionObserver()\n\n                        public class InjectionObserver: ObservableObject {\n                            @Published var injectionNumber = 0\n                            var cancellable: AnyCancellable? = nil\n                            let publisher = PassthroughSubject<Void, Never>()\n                            init() {\n                                _ = loadInjectionOnce // .enableInjection() optional Xcode 16+\n                                cancellable = NotificationCenter.default.publisher(for:\n                                    Notification.Name(\"\\(INJECTION_BUNDLE_NOTIFICATION)\"))\n                                    .sink { [weak self] change in\n                                    self?.injectionNumber += 1\n                                    self?.publisher.send()\n                                }\n                            }\n                        }\n\n                        extension SwiftUI.View {\n                            public func eraseToAnyView() -> some SwiftUI.View {\n                                _ = loadInjectionOnce\n                                return AnyView(self)\n                            }\n                            public func enableInjection() -> some SwiftUI.View {\n                                return eraseToAnyView()\n                            }\n                            public func loadInjection() -> some SwiftUI.View {\n                                return eraseToAnyView()\n                            }\n                            public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View {\n                                return self\n                                    .onReceive(injectionObserver.publisher, perform: bumpState)\n                                    .eraseToAnyView()\n                            }\n                        }\n\n                        @available(iOS 13.0, *)\n                        @propertyWrapper\n                        public struct ObserveInjection: DynamicProperty {\n                            @ObservedObject private var iO = injectionObserver\n                            public init() {}\n                            public private(set) var wrappedValue: Int {\n                                get {0} set {}\n                            }\n                        }\n                        #else\n                        extension SwiftUI.View {\n                            @inline(__always)\n                            public func eraseToAnyView() -> some SwiftUI.View { return self }\n                            @inline(__always)\n                            public func enableInjection() -> some SwiftUI.View { return self }\n                            @inline(__always)\n                            public func loadInjection() -> some SwiftUI.View { return self }\n                            @inline(__always)\n                            public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View {\n                                return self\n                            }\n                        }\n\n                        @available(iOS 13.0, *)\n                        @propertyWrapper\n                        public struct ObserveInjection {\n                            public init() {}\n                            public private(set) var wrappedValue: Int {\n                                get {0} set {}\n                            }\n                        }\n                        #endif\n                        #endif\n\n                        \"\"\"\n                }\n\n                if patched != original {\n                    try patched.write(to: fileURL,\n                                      atomically: false, encoding: .utf8)\n                }\n            }\n        }\n        catch {\n            print(error)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/injectiond/InjectionServer.swift",
    "content": "//\n//  InjectionServer.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/InjectionServer.swift#73 $\n//\n\nimport Cocoa\n#if SWIFT_PACKAGE\nimport HotReloadingGuts\nimport injectiondGuts\nimport XprobeUI\n#endif\n\nlet commandQueue = DispatchQueue(label: \"InjectionCommand\")\nlet compileQueue = DispatchQueue(label: \"InjectionCompile\")\n\nvar projectInjected = [String: [String: TimeInterval]]()\nlet MIN_INJECTION_INTERVAL = 1.0\n\npublic class InjectionServer: SimpleSocket {\n    // InjectionNext integration\n    static var clientQueue: DispatchQueue { commandQueue }\n    static var currentClient: InjectionServer? { appDelegate.lastConnection }\n    static var currentClients: [InjectionServer?] { [currentClient] }\n    var injectionNumber = 100\n    var exports = [String: [String]]()\n    var platform = \"iPhoneSimulator\"\n    var tmpPath: String { builder.tmpDir }\n    var arch: String { builder.arch }\n\n    var fileChangeHandler: ((_ changed: NSArray, _ ideProcPath:String) -> Void)!\n    var fileWatchers = [FileWatcher]()\n    var pause: TimeInterval = 0.0\n    var pending = [String]()\n    var builder = UnhidingEval()\n    var lastIdeProcPath = \"\"\n    let objcClassRefs = NSMutableArray()\n    let descriptorRefs = NSMutableArray()\n\n    open func log(_ msg: String) {\n        NSLog(\"\\(APP_PREFIX)\\(APP_NAME) \\(msg)\")\n    }\n\n    @discardableResult\n    override public class func error(_ message: String) -> Int32 {\n        let saveno = errno\n        let msg = String(format:message, strerror(saveno))\n        NSLog(\"\\(APP_PREFIX)\\(APP_NAME) \\(msg)\")\n        DispatchQueue.main.async {\n            let alert: NSAlert = NSAlert()\n            alert.messageText = \"\\(self)\"\n            alert.informativeText = msg\n            alert.alertStyle = .warning\n            alert.addButton(withTitle: \"OK\")\n            _ = alert.runModal()\n        }\n        return -1\n    }\n\n    func sendCommand(_ command: InjectionCommand, with string: String?) {\n        commandQueue.sync {\n            _ = writeCommand(command.rawValue, with: string)\n        }\n    }\n\n    func validateConnection() -> Bool {\n        return readInt() == INJECTION_SALT && readString() == INJECTION_KEY\n    }\n\n    @objc override public func runInBackground() {\n        var candiateProjectFile = appDelegate.selectedProject\n\n        if candiateProjectFile == nil {\n            DispatchQueue.main.sync {\n                appDelegate.openProject(self)\n            }\n            candiateProjectFile = appDelegate.selectedProject\n        }\n        guard let projectFile = candiateProjectFile else {\n            return\n        }\n\n        // tell client app the inferred project being watched\n        log(\"Connection for project file: \\(projectFile)\")\n\n        guard validateConnection() else {\n            log(\"*** Error: SALT or KEY invalid. Are you running start_daemon.sh or InjectionIII.app from the right directory?\")\n            write(\"/tmp\")\n            write(InjectionCommand.invalid.rawValue)\n            return\n        }\n\n        let ee = builder.evalError\n        defer {\n            builder.evalError = ee\n            builder.signer = nil\n        }\n\n        // client specific data for building\n        if let frameworks = readString() {\n            builder.frameworks = frameworks\n        } else { return }\n\n        if let arch = readString() {\n            builder.arch = arch\n        } else { return }\n\n        if appDelegate.isSandboxed {\n            builder.tmpDir = NSTemporaryDirectory()\n        } else {\n            builder.tmpDir = builder.frameworks\n        }\n        write(builder.tmpDir)\n        if !FileManager.default.fileExists(atPath: builder.tmpDir) {\n            builder.tmpDir = NSTemporaryDirectory()\n        }\n        log(\"Using tmp dir: \\(builder.tmpDir)\")\n\n        // log errors to client\n        builder.evalError = {\n            (message: String) in\n            self.log(\"evalError: \\(message)\")\n            self.sendCommand(.log, with:\n                (message.hasPrefix(\"Compiling\") ?\"\":\"⚠️ \")+message)\n            return NSError(domain:\"SwiftEval\", code:-1,\n                           userInfo:[NSLocalizedDescriptionKey: message])\n        }\n\n        builder.signer = {\n            let identity = appDelegate.defaults.string(forKey: projectFile)\n            if identity != nil {\n                self.log(\"Signing with identity: \\(identity!)\")\n            }\n            setenv(\"TOOLCHAIN_DIR\", self.builder.xcodeDev +\n                   \"/Toolchains/XcodeDefault.xctoolchain\", 1)\n            let dylib = self.builder.tmpDir+\"/eval\"+$0\n            var error = SignerService.codesignDylib(dylib, identity: identity)\n            if error != nil && self.isLocalClient {\n                error = SignerService.codesignDylib(dylib, identity: \"-\")\n            }\n            if let error = error {\n                self.sendCommand(.log, with:\"Codesigning failed with output: \" +\n                    error.trimmingCharacters(in: .whitespacesAndNewlines))\n                return false\n            }\n            return true\n        }\n\n        // Xcode specific config\n        if let xcodeDevURL = appDelegate.runningXcodeDevURL {\n            builder.xcodeDev = xcodeDevURL.path\n        }\n\n        builder.projectFile = projectFile\n\n        appDelegate.setMenuIcon(.ok)\n        appDelegate.lastConnection = self\n        pending = []\n\n        var lastInjected = projectInjected[projectFile]\n        if lastInjected == nil {\n            lastInjected = [String: Double]()\n            projectInjected[projectFile] = lastInjected!\n        }\n\n        guard let executable = readString() else { return }\n        if appDelegate.defaults.bool(forKey: UserDefaultsReplay) &&  \n            appDelegate.enableWatcher.state == .on {\n            let mtime = {\n                (path: String) -> time_t in\n                var info = stat()\n                return stat(path, &info) == 0 ? info.st_mtimespec.tv_sec : 0\n            }\n            let executableBuild = mtime(executable)\n            for (source, _) in lastInjected! {\n                if !source.hasSuffix(\"storyboard\") && !source.hasSuffix(\"xib\") &&\n                    mtime(source) > executableBuild {\n                    recompileAndInject(source: source)\n                }\n            }\n        }\n\n        builder.createUnhider(executable: executable,\n                              objcClassRefs, descriptorRefs)\n\n        var testCache = [String: [String]]()\n\n        fileChangeHandler = {\n            (changed: NSArray, ideProcPath: String) in\n            var changed = changed as! [String]\n\n            if UserDefaults.standard.bool(forKey: UserDefaultsTDDEnabled) {\n                for injectedFile in changed {\n                    var matchedTests = testCache[injectedFile]\n                    if matchedTests == nil {\n                        matchedTests = Self.searchForTestWithFile(injectedFile,\n                              projectRoot: appDelegate\n                                .watchedDirectories.first ??\n                              (projectFile as NSString)\n                                .deletingLastPathComponent,\n                            fileManager: FileManager.default)\n                        testCache[injectedFile] = matchedTests\n                    }\n\n                    changed += matchedTests!\n                }\n            }\n\n            let now = NSDate.timeIntervalSinceReferenceDate\n            let automatic = appDelegate.enableWatcher.state == .on\n            for swiftSource in changed {\n                if !self.pending.contains(swiftSource) {\n                    if (now > (lastInjected?[swiftSource] ?? 0.0) + MIN_INJECTION_INTERVAL && now > self.pause) {\n                        lastInjected![swiftSource] = now\n                        projectInjected[projectFile] = lastInjected!\n                        self.pending.append(swiftSource)\n                        if !automatic {\n                            let file = (swiftSource as NSString).lastPathComponent\n                            self.sendCommand(.log,\n                                with:\"'\\(file)' changed, type ctrl-= to inject\")\n                        }\n                    }\n                }\n            }\n            self.lastIdeProcPath = ideProcPath\n            self.builder.lastIdeProcPath = ideProcPath\n            if (automatic) {\n                self.injectPending()\n            }\n        }\n        defer { fileChangeHandler = nil }\n\n        // start up file watchers to write generated tmpfile path to client app\n        setProject(projectFile)\n        if projectFile.contains(\"/Desktop/\") || projectFile.contains(\"/Documents/\") {\n            sendCommand(.log, with: \"\\(APP_PREFIX)⚠️ Your project file seems to be in the Desktop or Documents folder and may prevent \\(APP_NAME) working as it has special permissions.\")\n        }\n\n        DispatchQueue.main.sync {\n            appDelegate.updateTraceInclude(nil)\n            appDelegate.updateTraceExclude(nil)\n            appDelegate.toggleFeedback(nil)\n            appDelegate.toggleLookup(nil)\n        }\n\n        if let appVersion = Bundle.main.infoDictionary?[\n            \"CFBundleShortVersionString\"] as? String {\n            sendCommand(.appVersion, with: appVersion)\n        }\n\n        // read status responses from client app\n        while true {\n            let commandInt = readInt()\n            guard let response = InjectionResponse(rawValue: commandInt) else {\n                log(\"InjectionServer: Unexpected case \\(commandInt)\")\n                break\n            }\n            if response == .exit {\n                break\n            }\n            process(response: response, executable: executable)\n        }\n\n        // client app disconnected\n        fileWatchers.removeAll()\n        appDelegate.traceItem.state = .off\n        appDelegate.setMenuIcon(.idle)\n    }\n\n    func process(response: InjectionResponse, executable: String) {\n            switch response {\n            case .frameworkList:\n                appDelegate.setFrameworks(readString() ?? \"\",\n                                          menuTitle: \"Trace Framework\")\n                appDelegate.setFrameworks(readString() ?? \"\",\n                                          menuTitle: \"Trace SysInternal\")\n                appDelegate.setFrameworks(readString() ?? \"\",\n                                          menuTitle: \"Trace Package\")\n            case .complete:\n                appDelegate.setMenuIcon(.ok)\n                if appDelegate.frontItem.state == .on {\n                    print(executable)\n                    let appToOrderFront: URL\n                    if executable.contains(\"/MacOS/\") {\n                        appToOrderFront = URL(fileURLWithPath: executable)\n                            .deletingLastPathComponent()\n                            .deletingLastPathComponent()\n                            .deletingLastPathComponent()\n                    } else if executable.contains(\"/Wrapper/\") {\n                        appToOrderFront = URL(fileURLWithPath: executable)\n                            .deletingLastPathComponent()\n                    } else {\n                        appToOrderFront = URL(fileURLWithPath: builder.xcodeDev)\n                            .appendingPathComponent(\"Applications/Simulator.app\")\n                    }\n                    NSWorkspace.shared.open(appToOrderFront)\n                }\n                break\n            case .pause:\n                pause = NSDate.timeIntervalSinceReferenceDate + Double(readString() ?? \"0.0\")!\n                break\n            case .getXcodeDev:\n                if let xcodeDev = readString() {\n                    builder.xcodeDev = xcodeDev\n                }\n            case .sign:\n                guard let signer = builder.signer,\n                    appDelegate.isSandboxed //|| xprobePlugin != nil\n                    else {\n                    sendCommand(.signed, with: \"0\")\n                    break\n                }\n                sendCommand(.signed, with: signer(readString() ?? \"\") ? \"1\": \"0\")\n            case .callOrderList:\n                if let calls = readString()?\n                    .components(separatedBy: CALLORDER_DELIMITER) {\n                    appDelegate.fileReorder(signatures: calls)\n                }\n                break\n            case .error:\n                appDelegate.setMenuIcon(.error)\n                log(\"Injection error: \\(readString() ?? \"Uknown\")\")\n            case .legacyUnhide:\n                builder.legacyUnhide = readString() == \"1\"\n            case .forceUnhide:\n                builder.startUnhide()\n            case .projectRoot:\n                if let projectRoot = readString() {\n                    DispatchQueue.main.async {\n                        _ = appDelegate.application(NSApp,\n                                                    openFile: projectRoot)\n                    }\n                }\n            case .buildCache:\n                if let buildCache = readString() {\n                    builder.buildCacheFile = buildCache\n                }\n            case .derivedData:\n                if let derived = readString() {\n                    setenv(INJECTION_DERIVED_DATA, derived, 1)\n                }\n            case .platform:\n                if let clientPlatform = readString() {\n                    platform = clientPlatform\n                }\n            default:\n                break\n            }\n    }\n\n    func recompileAndInject(source: String) {\n        sendCommand(.ideProcPath, with: lastIdeProcPath)\n        appDelegate.setMenuIcon(.busy)\n        if appDelegate.isSandboxed ||\n            source.hasSuffix(\".storyboard\") || source.hasSuffix(\".xib\") {\n            #if SWIFT_PACKAGE\n            try? source.write(toFile: \"/tmp/injecting_storyboard.txt\",\n                              atomically: false, encoding: .utf8)\n            #endif\n            sendCommand(.inject, with: source)\n        } else {\n            compileQueue.async {\n                do {\n                    let dylib = try self.prepare(source: source)\n                    self.sendCommand(.setXcodeDev, with: self.builder.xcodeDev)\n                    self.inject(dylib: dylib)\n                    return\n                } catch {\n                    NSLog(\"\\(APP_PREFIX)Build error: \\(error)\")\n                }\n                appDelegate.setMenuIcon(.error)\n                self.builder.updateLongTermCache(remove: source)\n            }\n        }\n    }\n\n    public func prepare(source: String) throws -> String {\n        #if INJECTION_III_APP\n        if source.hasSuffix(\".swift\") && !appDelegate.isSandboxed &&\n            appDelegate.updatePatchUnpatch() == .patched,\n           let prepared = NextCompiler.compileQueue.sync(execute: {\n               try? FrontendServer.frontendRecompiler()\n               .prepare(source: source, connected:\n                            InjectionServer.currentClient) }),\n           builder.signer?(prepared.dylibName[\"/eval\", \"\"]) == true {\n            FrontendServer.writeCache(for: prepared.platform)\n            return prepared.dylib[\".dylib$\", \"\"]\n        }\n        #endif\n        return try builder.rebuildClass(oldClass: nil,\n                  classNameOrFile: source, extra: nil)\n    }\n\n    public func inject(dylib: String) {\n        sendCommand(.load, with: dylib)\n    }\n\n    public func watchDirectory(_ directory: String) {\n        fileWatchers.append(FileWatcher(roots: [directory],\n                                        callback: fileChangeHandler))\n        sendCommand(.watching, with: directory)\n    }\n\n    @objc public func injectPending() {\n        for swiftSource in pending {\n            recompileAndInject(source: swiftSource)\n        }\n        pending.removeAll()\n    }\n\n    @objc public func setProject(_ projectFile: String) {\n        guard fileChangeHandler != nil else { return }\n\n        builder.projectFile = projectFile\n        #if !SWIFT_PACKAGE\n        let projectName = URL(fileURLWithPath: projectFile)\n            .deletingPathExtension().lastPathComponent\n        let derivedLogs = String(format: // legacy fallback of last resort removed\n            \"%@/NotLibrary/Developer/Xcode/DerivedData/%@-%@/Logs/Build\",\n                                 NSHomeDirectory(), projectName\n                                    .replacingOccurrences(of: #\"[\\s]+\"#, with:\"_\",\n                                                   options: .regularExpression),\n            XcodeHash.hashString(forPath: projectFile))\n        #else\n        let derivedLogs = appDelegate.derivedLogs ?? \"No derived logs\"\n        #endif\n        if FileManager.default.fileExists(atPath: derivedLogs) {\n            builder.derivedLogs = derivedLogs\n        }\n\n        sendCommand(.vaccineSettingChanged,\n                    with:appDelegate.vaccineConfiguration())\n        fileWatchers.removeAll()\n        sendCommand(.connected, with: projectFile)\n        for directory in appDelegate.watchedDirectories {\n            watchDirectory(directory)\n        }\n    }\n\n    class func searchForTestWithFile(_ injectedFile: String,\n            projectRoot: String, fileManager: FileManager) -> [String] {\n        var matchedTests = [String]()\n        let injectedFileName = URL(fileURLWithPath: injectedFile)\n            .deletingPathExtension().lastPathComponent\n        let projectUrl = URL(fileURLWithPath: projectRoot)\n        if let enumerator = fileManager.enumerator(at: projectUrl,\n                includingPropertiesForKeys: [URLResourceKey.nameKey,\n                                             URLResourceKey.isDirectoryKey],\n                options: .skipsHiddenFiles,\n                errorHandler: {\n                    (url: URL, error: Error) -> Bool in\n                    NSLog(\"[Error] \\(error) (\\(url))\")\n                    return false\n        }) {\n            for fileURL in enumerator {\n                var filename: AnyObject?\n                var isDirectory: AnyObject?\n                if let fileURL = fileURL as? NSURL {\n                    try! fileURL.getResourceValue(&filename, forKey:URLResourceKey.nameKey)\n                    try! fileURL.getResourceValue(&isDirectory, forKey:URLResourceKey.isDirectoryKey)\n\n                    if filename?.hasPrefix(\"_\") == true &&\n                        isDirectory?.boolValue == true {\n                        enumerator.skipDescendants()\n                        continue\n                    }\n\n                    if isDirectory?.boolValue == false  &&\n                        filename?.pathExtension == \".swift\",\n                        let lastPathComponent = filename?.lastPathComponent,\n                        lastPathComponent !=\n                            (injectedFile as NSString).lastPathComponent &&\n                        filename?.lowercased\n                            .contains(injectedFileName.lowercased()) == true &&\n                        (lastPathComponent.contains(\"Test\") == true ||\n                         lastPathComponent.contains(\"Spec.\") == true) {\n                        matchedTests.append(fileURL.path!)\n                    }\n                }\n            }\n        }\n\n        return matchedTests\n    }\n\n    public class func urlEncode(string: String) -> String {\n        let unreserved = \"-._~/?\"\n        var allowed = CharacterSet.alphanumerics\n        allowed.insert(charactersIn: unreserved)\n        return string.addingPercentEncoding(withAllowedCharacters: allowed)!\n    }\n\n    deinit {\n        log(\"\\(self).deinit()\")\n    }\n}\n"
  },
  {
    "path": "Sources/injectiond/UpdateCheck.swift",
    "content": "//\n//  UpdateCheck.swift\n//  InjectionIII\n//\n//  Created by John Holdsworth on 17/09/2020.\n//  Copyright © 2020 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/UpdateCheck.swift#3 $\n//\n\nimport Cocoa\n#if SWIFT_PACKAGE\nimport HotReloadingGuts\n#endif\n\nextension AppDelegate {\n\n    @IBAction func updateCheck(_ sender: NSMenuItem?) {\n        let userInitiated = sender != nil\n\n        URLSession(configuration: .default).dataTask(with:\n            URL(string:\n            \"https://api.github.com/repos/johnno1962/InjectionIII/releases\")!) {\n                data, response, error in\n            do {\n                if let data = data {\n                    let decoder = JSONDecoder()\n                    decoder.keyDecodingStrategy = .convertFromSnakeCase\n                    decoder.dateDecodingStrategy = .iso8601\n                    let releases = try decoder.decode([Release].self, from: data)\n\n                    DispatchQueue.main.async {\n                        guard let latest = releases\n                            .first(where: { !$0.prerelease }),\n                            let available = latest.tagName,\n                            let current = Bundle.main.object(\n                                forInfoDictionaryKey: \"CFBundleShortVersionString\")\n                                as? String,\n                            available.compare(current, options: .numeric)\n                                == .orderedDescending else {\n                            if userInitiated {\n                                let alert = NSAlert()\n                                alert.addButton(withTitle: \"OK\")\n                                alert.addButton(withTitle: \"Check Monthly\")\n                                alert.messageText = \"You are running the latest released version.\"\n                                switch alert.runModal() {\n                                case .alertSecondButtonReturn:\n                                    self.updateItem.state = .on\n                                default:\n                                    break\n                                }\n                            }\n                            self.setUpdateCheck()\n                            return\n                        }\n\n                        let fmt = DateFormatter()\n                        fmt.dateStyle = .medium\n                        let alert = NSAlert()\n                        alert.messageText = \"New build \\(available) (\\(fmt.string(from: latest.publishedAt))) is available.\"\n                        alert.informativeText = latest.body\n                        alert.addButton(withTitle: \"View\")\n                        alert.addButton(withTitle: \"Download\")\n                        alert.addButton(withTitle: \"Later\")\n                        switch alert.runModal() {\n                        case .alertFirstButtonReturn:\n                            NSWorkspace.shared.open(latest.htmlUrl)\n                        case .alertSecondButtonReturn:\n                            NSWorkspace.shared.open(latest\n                                .assets[0].browserDownloadUrl)\n                        default:\n                            break\n                        }\n                        self.setUpdateCheck()\n                    }\n                }\n                else if let error = error {\n                    throw error\n                }\n            } catch {\n                DispatchQueue.main.async {\n                    NSAlert(error: error).runModal()\n                }\n            }\n        }.resume()\n    }\n\n    func setUpdateCheck() {\n        if updateItem.state == .on {\n            defaults.set(Date.timeIntervalSinceReferenceDate +\n                         30 * 24 * 60 * 60, forKey: UserDefaultsUpdateCheck)\n        }\n    }\n\n    struct Release: Decodable {\n        let htmlUrl: URL\n        let tagName: String?\n        let prerelease: Bool\n        let publishedAt: Date\n        let assets: [Asset]\n        let body: String\n    }\n\n    struct Asset: Decodable {\n        let browserDownloadUrl: URL\n    }\n}\n"
  },
  {
    "path": "Sources/injectiond/main.swift",
    "content": "//\n//  main.swift\n//  HotReloading\n//\n//  Created by John Holdsworth on 02/24/2021.\n//  Copyright © 2021 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiond/main.swift#19 $\n//\n//  Server daemon side of HotReloading simulating InjectionIII.app.\n//\n\nimport Cocoa\n\n// Launch as a Cocoa app\nvar argv = CommandLine.arguments.map { $0.withCString { strdup($0) } }\n\nargv.withUnsafeMutableBufferPointer {\n    _ = NSApplicationMain(Int32($0.count), $0.baseAddress!)\n}\n"
  },
  {
    "path": "Sources/injectiondGuts/SignerService.m",
    "content": "//\n//  SignerService.m\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n//  $Id: //depot/HotReloading/Sources/injectiondGuts/SignerService.m#18 $\n//\n\n#import \"SignerService.h\"\n\n@implementation SignerService\n\n+ (NSString *)codesignDylib:(NSString *)dylib identity:(NSString *)identity {\n    static NSString *adhocSign = @\"-\";\n    const char *envIdentity = getenv(\"EXPANDED_CODE_SIGN_IDENTITY\")\n                            ?: getenv(\"CODE_SIGN_IDENTITY\");\n    const char *toolchainDir = getenv(\"TOOLCHAIN_DIR\") ?:\n        \"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain\";\n    if (envIdentity && strlen(envIdentity)) {\n        identity = [NSString stringWithFormat:@\"\\\"%s\\\"\", envIdentity];\n        NSLog(@\"Signing identity from environment: %@\", identity);\n    }\n    NSString *command = [NSString stringWithFormat:@\"\"\n                         \"(export CODESIGN_ALLOCATE=\\\"%s/usr/bin/codesign_allocate\\\"; \"\n                         \"if /usr/bin/file \\\"%@\\\" | /usr/bin/grep ' shared library ' >/dev/null;\"\n                         \"then /usr/bin/codesign --force -s %@ \\\"%@\\\";\"\n                         \"else echo BAD-FILE; exit 1; fi) 2>&1\",\n                         toolchainDir, dylib, identity ?: adhocSign, dylib];\n    FILE *fp = popen(command.UTF8String, \"r\");\n    if (!fp)\n        return @\"Could not popen() for codesign\";\n    NSMutableString *err = [NSMutableString new];\n    char buffer[1000];\n    while (fgets(buffer, sizeof buffer, fp))\n        [err appendFormat:@\"%s\", buffer];\n    if (pclose(fp) >> 8 == EXIT_SUCCESS)\n        return nil;\n    NSLog(@\"*** Codesign failed with command: %@ Error: %@\", command, err);\n    return err;\n}\n\n#if 0 // no longer used\n- (void)runInBackground {\n    char __unused skip, buffer[1000];\n    buffer[read(clientSocket, buffer, sizeof buffer-1)] = '\\000';\n    NSString *path = [[NSString stringWithUTF8String:buffer] componentsSeparatedByString:@\" \"][1];\n\n    if ([[self class] codesignDylib:path identity:nil]) {\n        snprintf(buffer, sizeof buffer, \"HTTP/1.0 200 OK\\r\\n\\r\\n\");\n        write(clientSocket, buffer, strlen(buffer));\n    }\n}\n#endif\n\n@end\n"
  },
  {
    "path": "Sources/injectiondGuts/include/SignerService.h",
    "content": "//\n//  SignerService.h\n//  InjectionIII\n//\n//  Created by John Holdsworth on 06/11/2017.\n//  Copyright © 2017 John Holdsworth. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n\n@interface SignerService: NSObject\n\n+ (NSString * _Nullable)codesignDylib:(NSString * _Nonnull)dylib identity:(NSString * _Nullable)identity;\n\n@end\n"
  },
  {
    "path": "Sources/injectiondGuts/include/Xcode.h",
    "content": "/*\n * Xcode.h -- extracted using: sdef /Applications/Xcode.app | sdp -fh --basename Xcode\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class XcodeApplication, XcodeDocument, XcodeWindow, XcodeFileDocument, XcodeTextDocument, XcodeSourceDocument, XcodeWorkspaceDocument, XcodeSchemeActionResult, XcodeSchemeActionIssue, XcodeBuildError, XcodeBuildWarning, XcodeAnalyzerIssue, XcodeTestFailure, XcodeScheme, XcodeRunDestination, XcodeDevice, XcodeBuildConfiguration, XcodeProject, XcodeBuildSetting, XcodeResolvedBuildSetting, XcodeTarget;\n\n// Category added for use with Swift\n@interface SBObject(InjectionIII)\n- (id) open:(id)x;  // Open a document.\n@property (copy) NSString *path;  // The document's path.\n@property (copy) NSArray<NSNumber *> *selectedCharacterRange;  // The first and last character positions in the selection.\n@property (copy) XcodeWorkspaceDocument *activeWorkspaceDocument;  // The active workspace document in Xcode.\n@end\n\nenum XcodeSaveOptions {\n\tXcodeSaveOptionsYes = 'yes ' /* Save the file. */,\n\tXcodeSaveOptionsNo = 'no  ' /* Do not save the file. */,\n\tXcodeSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */\n};\ntypedef enum XcodeSaveOptions XcodeSaveOptions;\n\n// The status of a scheme action result object.\nenum XcodeSchemeActionResultStatus {\n\tXcodeSchemeActionResultStatusNotYetStarted = 'srsn' /* The action has not yet started. */,\n\tXcodeSchemeActionResultStatusRunning = 'srsr' /* The action is in progress. */,\n\tXcodeSchemeActionResultStatusCancelled = 'srsc' /* The action was cancelled. */,\n\tXcodeSchemeActionResultStatusFailed = 'srsf' /* The action ran but did not complete successfully. */,\n\tXcodeSchemeActionResultStatusErrorOccurred = 'srse' /* The action was not able to run due to an error. */,\n\tXcodeSchemeActionResultStatusSucceeded = 'srss' /* The action succeeded. */\n};\ntypedef enum XcodeSchemeActionResultStatus XcodeSchemeActionResultStatus;\n\n@protocol XcodeGenericMethods\n\n- (void) closeSaving:(XcodeSaveOptions)saving savingIn:(NSURL *)savingIn;  // Close a document.\n- (void) delete;  // Delete an object.\n- (void) moveTo:(SBObject *)to;  // Move an object to a new location.\n- (XcodeSchemeActionResult *) build;  // Invoke the \"build\" scheme action. This command should be sent to a workspace document. The build will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result.\n- (XcodeSchemeActionResult *) clean;  // Invoke the \"clean\" scheme action. This command should be sent to a workspace document. The clean will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result.\n- (void) stop;  // Stop the active scheme action, if one is running. This command should be sent to a workspace document. This command does not wait for the action to stop.\n- (XcodeSchemeActionResult *) runWithCommandLineArguments:(id)withCommandLineArguments withEnvironmentVariables:(id)withEnvironmentVariables;  // Invoke the \"run\" scheme action. This command should be sent to a workspace document. The run action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result.\n- (XcodeSchemeActionResult *) testWithCommandLineArguments:(id)withCommandLineArguments withEnvironmentVariables:(id)withEnvironmentVariables;  // Invoke the \"test\" scheme action. This command should be sent to a workspace document. The test action will be performed using the workspace document's current active scheme and active run destination. This command does not wait for the action to complete; its progress can be tracked with the returned scheme action result.\n\n@end\n\n\n\n/*\n * Standard Suite\n */\n\n// The application's top-level scripting object.\n@interface XcodeApplication : SBApplication\n\n- (SBElementArray<XcodeDocument *> *) documents;\n- (SBElementArray<XcodeWindow *> *) windows;\n\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (readonly) BOOL frontmost;  // Is this the active application?\n@property (copy, readonly) NSString *version;  // The version number of the application.\n\n- (id) open:(id)x;  // Open a document.\n- (void) quitSaving:(XcodeSaveOptions)saving;  // Quit the application.\n- (BOOL) exists:(id)x;  // Verify that an object exists.\n\n@end\n\n// A document.\n@interface XcodeDocument : SBObject <XcodeGenericMethods>\n\n@property (copy, readonly) NSString *name;  // Its name.\n@property (readonly) BOOL modified;  // Has it been modified since the last save?\n@property (copy, readonly) NSURL *file;  // Its location on disk, if it has one.\n\n\n@end\n\n// A window.\n@interface XcodeWindow : SBObject <XcodeGenericMethods>\n\n@property (copy, readonly) NSString *name;  // The title of the window.\n- (NSInteger) id;  // The unique identifier of the window.\n@property NSInteger index;  // The index of the window, ordered front to back.\n@property NSRect bounds;  // The bounding rectangle of the window.\n@property (readonly) BOOL closeable;  // Does the window have a close button?\n@property (readonly) BOOL miniaturizable;  // Does the window have a minimize button?\n@property BOOL miniaturized;  // Is the window minimized right now?\n@property (readonly) BOOL resizable;  // Can the window be resized?\n@property BOOL visible;  // Is the window visible right now?\n@property (readonly) BOOL zoomable;  // Does the window have a zoom button?\n@property BOOL zoomed;  // Is the window zoomed right now?\n@property (copy, readonly) XcodeDocument *document;  // The document whose contents are displayed in the window.\n\n\n@end\n\n\n\n/*\n * Xcode Application Suite\n */\n\n// The Xcode application.\n@interface XcodeApplication (XcodeApplicationSuite)\n\n- (SBElementArray<XcodeFileDocument *> *) fileDocuments;\n- (SBElementArray<XcodeSourceDocument *> *) sourceDocuments;\n- (SBElementArray<XcodeWorkspaceDocument *> *) workspaceDocuments;\n\n@property (copy) XcodeWorkspaceDocument *activeWorkspaceDocument;  // The active workspace document in Xcode.\n\n@end\n\n\n\n/*\n * Xcode Document Suite\n */\n\n// An Xcode-compatible document.\n@interface XcodeDocument (XcodeDocumentSuite)\n\n@property (copy) NSString *path;  // The document's path.\n\n@end\n\n// A document that represents a file on disk. It also provides access to the window it appears in.\n@interface XcodeFileDocument : XcodeDocument\n\n\n@end\n\n// A document that represents a text file on disk. It also provides access to the window it appears in.\n@interface XcodeTextDocument : XcodeFileDocument\n\n@property (copy) NSArray<NSNumber *> *selectedCharacterRange;  // The first and last character positions in the selection.\n@property (copy) NSArray<NSNumber *> *selectedParagraphRange;  // The first and last paragraph positions that contain the selection.\n@property (copy) NSString *text;  // The text of the text file referenced.\n@property BOOL notifiesWhenClosing;  // Should Xcode notify other apps when this document is closed?\n\n\n@end\n\n// A document that represents a source file on disk. It also provides access to the window it appears in.\n@interface XcodeSourceDocument : XcodeTextDocument\n\n\n@end\n\n// A document that represents a workspace on disk. Workspaces are the top-level container for almost all objects and commands in Xcode.\n@interface XcodeWorkspaceDocument : XcodeDocument\n\n- (SBElementArray<XcodeProject *> *) projects;\n- (SBElementArray<XcodeScheme *> *) schemes;\n- (SBElementArray<XcodeRunDestination *> *) runDestinations;\n\n@property BOOL loaded;  // Whether the workspace document has finsished loading after being opened. Messages sent to a workspace document before it has loaded will result in errors.\n@property (copy) XcodeScheme *activeScheme;  // The workspace's scheme that will be used for scheme actions.\n@property (copy) XcodeRunDestination *activeRunDestination;  // The workspace's run destination that will be used for scheme actions.\n@property (copy) XcodeSchemeActionResult *lastSchemeActionResult;  // The scheme action result for the last scheme action command issued to the workspace document.\n@property (copy, readonly) NSURL *file;  // The workspace document's location on disk, if it has one.\n\n\n@end\n\n\n\n/*\n * Xcode Scheme Suite\n */\n\n// An object describing the result of performing a scheme action command.\n@interface XcodeSchemeActionResult : SBObject <XcodeGenericMethods>\n\n- (SBElementArray<XcodeBuildError *> *) buildErrors;\n- (SBElementArray<XcodeBuildWarning *> *) buildWarnings;\n- (SBElementArray<XcodeAnalyzerIssue *> *) analyzerIssues;\n- (SBElementArray<XcodeTestFailure *> *) testFailures;\n\n- (NSString *) id;  // The unique identifier for the scheme.\n@property (readonly) BOOL completed;  // Whether this scheme action has completed (sucessfully or otherwise) or not.\n@property XcodeSchemeActionResultStatus status;  // Indicates the status of the scheme action.\n@property (copy) NSString *errorMessage;  // If the result's status is \"error occurred\", this will be the error message; otherwise, this will be \"missing value\".\n@property (copy) NSString *buildLog;  // If this scheme action performed a build, this will be the text of the build log.\n\n\n@end\n\n// An issue (like an error or warning) generated by a scheme action.\n@interface XcodeSchemeActionIssue : SBObject <XcodeGenericMethods>\n\n@property (copy) NSString *message;  // The text of the issue.\n@property (copy) NSString *filePath;  // The file path where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file.\n@property NSInteger startingLineNumber;  // The starting line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file.\n@property NSInteger endingLineNumber;  // The ending line number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file.\n@property NSInteger startingColumnNumber;  // The starting column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file.\n@property NSInteger endingColumnNumber;  // The ending column number in the file where the issue occurred. This may be 'missing value' if the issue is not associated with a specific source file.\n\n\n@end\n\n// An error generated by a build.\n@interface XcodeBuildError : XcodeSchemeActionIssue\n\n\n@end\n\n// A warning generated by a build.\n@interface XcodeBuildWarning : XcodeSchemeActionIssue\n\n\n@end\n\n// A warning generated by the static analyzer.\n@interface XcodeAnalyzerIssue : XcodeSchemeActionIssue\n\n\n@end\n\n// A failure from a test.\n@interface XcodeTestFailure : XcodeSchemeActionIssue\n\n\n@end\n\n// A set of parameters for building, testing, launching or distributing the products of a workspace.\n@interface XcodeScheme : SBObject <XcodeGenericMethods>\n\n@property (copy, readonly) NSString *name;  // The name of the scheme.\n- (NSString *) id;  // The unique identifier for the scheme.\n\n\n@end\n\n// An object which specifies parameters such as the device and architecture for which to perform a scheme action.\n@interface XcodeRunDestination : SBObject <XcodeGenericMethods>\n\n@property (copy, readonly) NSString *name;  // The name of the run destination, as displayed in Xcode's interface.\n@property (copy, readonly) NSString *architecture;  // The architecture for which this run destination results in execution.\n@property (copy, readonly) NSString *platform;  // The identifier of the platform which this run destination targets, such as \"macosx\", \"iphoneos\", \"iphonesimulator\", etc .\n@property (copy, readonly) XcodeDevice *device;  // The physical or virtual device which this run destination targets.\n@property (copy, readonly) XcodeDevice *companionDevice;  // If the run destination's device has a companion (e.g. a paired watch for a phone) which it will use, this is that device.\n\n\n@end\n\n// A device which can be used as the target for a scheme action, as part of a run destination.\n@interface XcodeDevice : SBObject <XcodeGenericMethods>\n\n@property (copy, readonly) NSString *name;  // The name of the device.\n@property (copy, readonly) NSString *deviceIdentifier;  // A stable identifier for the device, as shown in Xcode's \"Devices\" window.\n@property (copy, readonly) NSString *operatingSystemVersion;  // The version of the operating system installed on the device which this run destination targets.\n@property (copy, readonly) NSString *deviceModel;  // The model of device (e.g. \"iPad Air\") which this run destination targets.\n@property (readonly) BOOL generic;  // Whether this run destination is generic instead of representing a specific device. Most destinations are not generic, but a generic destination (such as \"Generic iOS Device\") will be available for some platforms if no physical devices are connected.\n\n\n@end\n\n\n\n/*\n * Xcode Project Suite\n */\n\n// A set of build settings for a target or project. Each target in a project has the same named build configurations as the project.\n@interface XcodeBuildConfiguration : SBObject <XcodeGenericMethods>\n\n- (SBElementArray<XcodeBuildSetting *> *) buildSettings;\n- (SBElementArray<XcodeResolvedBuildSetting *> *) resolvedBuildSettings;\n\n- (NSString *) id;  // The unique identifier for the build configuration.\n@property (copy, readonly) NSString *name;  // The name of the build configuration.\n\n\n@end\n\n// An Xcode project. Projects represent project files on disk and are always open in the context of a workspace document.\n@interface XcodeProject : SBObject <XcodeGenericMethods>\n\n- (SBElementArray<XcodeBuildConfiguration *> *) buildConfigurations;\n- (SBElementArray<XcodeTarget *> *) targets;\n\n@property (copy, readonly) NSString *name;  // The name of the project\n- (NSString *) id;  // The unique identifier for the project.\n\n\n@end\n\n// A setting that controls how products are built.\n@interface XcodeBuildSetting : SBObject <XcodeGenericMethods>\n\n@property (copy) NSString *name;  // The unlocalized build setting name (e.g. DSTROOT).\n@property (copy) NSString *value;  // A string value for the build setting.\n\n\n@end\n\n// An object that represents a resolved value for a build setting.\n@interface XcodeResolvedBuildSetting : SBObject <XcodeGenericMethods>\n\n@property (copy) NSString *name;  // The unlocalized build setting name (e.g. DSTROOT).\n@property (copy) NSString *value;  // A string value for the build setting.\n\n\n@end\n\n// A target is a blueprint for building a product. Targets inherit build settings from their project if not overridden in the target.\n@interface XcodeTarget : SBObject <XcodeGenericMethods>\n\n- (SBElementArray<XcodeBuildConfiguration *> *) buildConfigurations;\n\n@property (copy) NSString *name;  // The name of this target.\n- (NSString *) id;  // The unique identifier for the target.\n@property (copy, readonly) XcodeProject *project;  // The project that contains this target\n\n\n@end\n\n"
  },
  {
    "path": "copy_bundle.sh",
    "content": "#!/bin/bash -x\n#\n#  copy_bundle.sh\n#  InjectionIII\n#\n#  Copies injection bundle for on-device injection.\n#  Thanks @oryonatan\n#\n#  $Id: //depot/HotReloading/copy_bundle.sh#18 $\n#\n\nif [[ \"$CONFIGURATION\" =~ Debug ]]; then\n    if [ ! -w \"$CODESIGNING_FOLDER_PATH\" ]; then\n        echo '*** copy_bundle.sh unable to write to file system. ***'\n            'Change build setting \"User Script Sandboxing\" to NO'\n        exit 1;\n    fi\n\n    RESOURCES=${RESOURCES:-\"$(dirname \"$0\")\"}\n    COPY=\"$CODESIGNING_FOLDER_PATH/iOSInjection.bundle\"\n    STRACE=\"$COPY/Frameworks/SwiftTrace.framework/SwiftTrace\"\n    PLIST=\"$COPY/Info.plist\"\n    if [ \"$PLATFORM_NAME\" == \"macosx\" ]; then\n     BUNDLE=${1:-macOSInjection}\n     COPY=\"$CODESIGNING_FOLDER_PATH/Contents/Resources/macOSInjection.bundle\"\n     STRACE=\"$COPY/Contents/Frameworks/SwiftTrace.framework/Versions/A/SwiftTrace\"\n     PLIST=\"$COPY/Contents/Info.plist\"\n    elif [ \"$PLATFORM_NAME\" == \"appletvsimulator\" ]; then\n     BUNDLE=${1:-tvOSInjection}\n    elif [ \"$PLATFORM_NAME\" == \"appletvos\" ]; then\n     BUNDLE=${1:-tvdevOSInjection}\n    elif [ \"$PLATFORM_NAME\" == \"xrsimulator\" ]; then\n     BUNDLE=${1:-xrOSInjection}\n    elif [ \"$PLATFORM_NAME\" == \"watchsimulator\" ]; then\n     BUNDLE=${1:-watchOSInjection}\n    elif [ \"$PLATFORM_NAME\" == \"xros\" ]; then\n     BUNDLE=${1:-xrdevOSInjection}\n    elif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]; then\n     BUNDLE=${1:-maciOSInjection}\n     rsync -a \"$PLATFORM_DEVELOPER_LIBRARY_DIR\"/{Frameworks,PrivateFrameworks}/XC* \"$PLATFORM_DEVELOPER_USR_DIR/lib\"/*.dylib \"$COPY/Frameworks/\" &&\n     codesign -f --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" --timestamp\\=none --preserve-metadata\\=identifier,entitlements,flags --generate-entitlement-der \"$COPY/Frameworks\"/{XC*,*.dylib};\n    else\n     BUNDLE=${1:-iOSInjection}\n    fi\n    rsync -a \"$RESOURCES/$BUNDLE.bundle\"/* \"$COPY/\" &&\n    /usr/libexec/PlistBuddy -c \"Add :UserHome string $HOME\" \"$PLIST\" &&\n    codesign -f --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" --timestamp\\=none --preserve-metadata\\=identifier,entitlements,flags --generate-entitlement-der \"$STRACE\" \"$COPY\" &&\n    defaults write com.johnholdsworth.InjectionIII \"$PROJECT_FILE_PATH\" $EXPANDED_CODE_SIGN_IDENTITY\nfi\n"
  },
  {
    "path": "fix_previews.sh",
    "content": "#!/bin/bash\n#\n#  Workaround for limitations of Xcode Previews\n#  for projects that have dynamic SPM libraries.\n#  Running this script seemed to help Xcode not\n#  leave out frameworks when running previews.\n#\n#  $Id: //depot/HotReloading/fix_previews.sh#7 $\n#\n\necho \"*** You no longer need to run fix_previews.sh\" 1>&2\n\nAPP_ROOT=\"$CODESIGNING_FOLDER_PATH\"\nif [ -d \"$APP_ROOT\"/Contents ]; then\n    APP_ROOT=\"$APP_ROOT\"/Contents\nfi\n\nmkdir \"$APP_ROOT\"/Frameworks\n\nfor framework in \"$CODESIGNING_FOLDER_PATH\"/../PackageFrameworks/*.framework; do\n    cp -r \"$framework\" \"$APP_ROOT\"/Frameworks >>/tmp/fix_previews.txt 2>&1\ndone\n\nexit 0\n"
  },
  {
    "path": "start_daemon.sh",
    "content": "#!/bin/bash\n#\n# Start up daemon process to rebuild changed sources\n#\n# $Id: //depot/HotReloading/start_daemon.sh#42 $\n#\n\necho \"*** You no longer need to run start_daemon.sh\" 1>&2\n\n# You used to use this script in a \"Run Script/Build Phase\" like this:\n#if [ -d $SYMROOT/../../SourcePackages ]; then\n#    $SYMROOT/../../SourcePackages/checkouts/HotReloading/start_daemon.sh\n#fi\n\nexport PROJECT_FILE_PATH=\"${PROJECT_FILE_PATH:-\"$PWD/Package.swift\"}\" # Vapor\n\ncd \"$(dirname \"$0\")\"\n\nif [ \"$CONFIGURATION\" = \"Release\" ]; then\n    echo \"error: You shouldn't be shipping HotReloading in your app!\"\n    exit 1\nfi\n\nif [ -f \"/tmp/injecting_storyboard.txt\" ]; then\n    rm /tmp/injecting_storyboard.txt\n    exit 0\nfi\n\nexport SYMROOT=\"${SYMROOT:-$(dirname \"$PWD\")}\" # Vapor\n\nDERIVED_DATA=\"$(dirname $(dirname $SYMROOT))\"\nexport DERIVED_LOGS=\"$DERIVED_DATA/Logs/Build\"\n\nLAST_LOG=`ls -t $DERIVED_LOGS/*.xcactivitylog | head -n 1`\n\nexport NORMAL_ARCH_FILE=\"$OBJECT_FILE_DIR_normal/$ARCHS/$PRODUCT_NAME\"\nexport LINK_FILE_LIST=\"$NORMAL_ARCH_FILE.LinkFileList\"\n\n# kill any existing daemon process\nkill -9 `ps auxww | grep .build/debug/injectiond | grep -v grep | awk '{ print $2 }'`\n\n# Avoid having to fetch dependancies again\n# mkdir -p .build; ln -s \"$DERIVED_DATA\"/SourcePackages/repositories .build\n\n# rebuild daemon\n/usr/bin/env -i PATH=\"$PATH\" \"$TOOLCHAIN_DIR\"/usr/bin/swift build --product injectiond &&\n\n# clone Contents directory for Cocoa\nrsync -at Contents .build/debug &&\n\n# run in background passing project file, logs directory\n# followed by a list of additional directories to watch.\n# when working with a .xcworkspace set PROJECT_FILE_PATH\n# to the path to the workspace file in the build phase.\n(.build/debug/injectiond \"$PROJECT_FILE_PATH\" \"$DERIVED_LOGS\" `gunzip <$LAST_LOG | tr '\\r' '\\n' | grep -e '  cd ' | sort -u | grep -v DerivedData | awk '{ print $2 }'` >/tmp/hot_reloading.log 2>&1 &)\n"
  }
]