├── .github └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── AppCodePlugin ├── .idea │ ├── ant.xml │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── dictionaries │ │ └── johnholdsworth.xml │ ├── encodings.xml │ ├── misc.xml │ ├── modules.xml │ ├── scopes │ │ └── scope_settings.xml │ ├── uiDesigner.xml │ ├── vcs.xml │ └── workspace.xml ├── INSTALL.md ├── Injection.iml ├── META-INF │ └── plugin.xml └── src │ └── com │ └── injectionforxcode │ └── InjectionAction.java ├── BAZEL.md ├── Bootstrap ├── Info.plist ├── mach_inject_bundle_stub.c └── mach_inject_bundle_stub.h ├── DDHotKey ├── DDHotKeyCenter.h ├── DDHotKeyCenter.m ├── DDHotKeyUtilities.h ├── DDHotKeyUtilities.m └── README.markdown ├── EvalApp ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── EvalApp.entitlements └── Info.plist ├── Helper ├── Helper.h ├── Helper.m ├── Info-Launchd.plist ├── Info.plist ├── mach_inject.c ├── mach_inject.h └── main.m ├── InjectionBundle ├── Info.plist ├── InjectionClient.mm └── XprobeSwift-Bridging-Header.h ├── InjectionIII.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── InjectionIII.xcscheme ├── InjectionIII ├── App.icns ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── Credits.rtf ├── HelperInstaller.h ├── HelperInstaller.m ├── HelperProxy.h ├── HelperProxy.m ├── Info.plist ├── InjectionBusy.tif ├── InjectionError.tif ├── InjectionIII-Bridging-Header.h ├── InjectionIII.entitlements ├── InjectionIIISalt.h ├── InjectionIdle.tif ├── InjectionOK.tif ├── UpdateCheck.swift ├── Xcode.h ├── XcodeHash.h ├── XcodeHash.m ├── build_bundles.sh ├── main.m └── swift-frontend.sh ├── LICENSE ├── OLDME.md ├── README.md ├── README_Chinese.md ├── ROADMAP.md ├── SwiftEval ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DetailViewController.swift ├── Info.plist ├── MasterViewController.swift └── SwiftEval.entitlements ├── SwiftEvalTests ├── Info.plist └── SwiftEvalTests.swift ├── SwiftUISupport ├── Info.plist ├── SwiftUISupport-Bridging-Header.h └── SwiftUISupport.swift ├── interposable.png └── signer ├── SignerService.h ├── SignerService.m └── main.m /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: johnno1962 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *Library/* 3 | *xcuserdata* 4 | InjectionIII * 5 | DerivedData 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SwiftTrace"] 2 | path = SwiftTrace 3 | url = https://github.com/johnno1962/SwiftTrace 4 | [submodule "HotReloading"] 5 | path = HotReloading 6 | url = https://github.com/johnno1962/HotReloading.git 7 | [submodule "DLKit"] 8 | path = DLKit 9 | url = https://github.com/johnno1962/DLKit 10 | [submodule "SwiftRegex5"] 11 | path = SwiftRegex5 12 | url = https://github.com/johnno1962/SwiftRegex5 13 | [submodule "ProfileSwiftUI"] 14 | path = ProfileSwiftUI 15 | url = https://github.com/johnno1962/ProfileSwiftUI 16 | [submodule "InjectionNext"] 17 | path = InjectionNext 18 | url = http://github.com/johnno1962/InjectionNext 19 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/dictionaries/johnholdsworth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /AppCodePlugin/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AppCodePlugin/INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Plugin to use InjectionIII inside AppCode 2 | 3 | To install, download the file `Injection.jar` and use the ⚙️ icon in AppCode/Preferences/Plugins to `Install plugin from disk...`. A new item will appear at the end of the `Run` menu, `Inject Source` after restarting AppCode. This can be used once a program is running and indexing has completed. It has a keyboard shortcut of control-=. You still need to add `"-Xlinker -interposable"` to your project's `"Other Linker Flags"` for the simulator Debug target. This plugin should be used instead of running the InjectionIII application once it is installed as it shares the same port. 4 | 5 | Also available from the [Jetbrains plugin store](https://plugins.jetbrains.com/plugin/7187-injectioniii-for-appcode/). 6 | -------------------------------------------------------------------------------- /AppCodePlugin/Injection.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AppCodePlugin/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.injectionforxcode.injection.plugin.id 3 | InjectionIII for AppCode 4 | 4.1 5 | Injection for Xcode 6 | 7 | 9 | Requires InjectionIII from Mac App Store to work. 10 | ]]> 11 | 12 | 14 | ]]> 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | com.intellij.modules.lang 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AppCodePlugin/src/com/injectionforxcode/InjectionAction.java: -------------------------------------------------------------------------------- 1 | package com.injectionforxcode; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 6 | import com.intellij.openapi.fileEditor.FileDocumentManager; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.openapi.ui.Messages; 10 | import com.intellij.util.ui.UIUtil; 11 | 12 | import java.net.*; 13 | import java.io.*; 14 | 15 | /** 16 | * Copyright (c) 2013 John Holdsworth. All rights reserved. 17 | * 18 | * $Id: //depot/ResidentEval/AppCodePlugin/src/com/injectionforxcode/InjectionAction.java#3 $ 19 | * 20 | * Created with IntelliJ IDEA. 21 | * Date: 24/02/2013 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 27 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 28 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | * 31 | * If you want to "support the cause", consider a paypal donation to: 32 | * 33 | * Revised 2020 for use with InjectionIII. Seems to only build with 34 | * old versions of IntelliJ community edition e.g. IntelliJ IDEA 15 CE 35 | * 36 | * injectionforxcode@johnholdsworth.com 37 | * 38 | */ 39 | 40 | public class InjectionAction extends AnAction { 41 | 42 | static String bundlePath = "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle"; 43 | static short INJECTION_PORT = 8898; 44 | static String CHARSET = "UTF-8"; 45 | 46 | enum InjectionCommand { 47 | Connected, Watching, Log, Signed, Load, Inject, ProcPath, Xprobe, Eval, VaccineSettingChanged, Trace, Untrace 48 | } 49 | enum InjectionResponse { 50 | Complete, Pause, Sign, Error 51 | } 52 | 53 | static InjectionAction plugin; 54 | 55 | { 56 | startServer(INJECTION_PORT); 57 | plugin = this; 58 | } 59 | 60 | public void actionPerformed(AnActionEvent event) { 61 | injectFile(event); 62 | } 63 | 64 | static int alert(final String msg) { 65 | UIUtil.invokeAndWaitIfNeeded(new Runnable() { 66 | public void run() { 67 | Messages.showMessageDialog(msg, "Injection Plugin", Messages.getInformationIcon()); 68 | } 69 | }); 70 | return 0; 71 | } 72 | 73 | static void error(String where, Throwable e) { 74 | alert(where + ": " + e + " " + e.getMessage()); 75 | throw new RuntimeException("Injection Plugin error", e); 76 | } 77 | 78 | void startServer(int portNumber) { 79 | try { 80 | final ServerSocket serverSocket = new ServerSocket(); 81 | serverSocket.setReuseAddress(true); 82 | serverSocket.bind(new InetSocketAddress(portNumber),5); 83 | 84 | new Thread(new Runnable() { 85 | public void run() { 86 | while (true) { 87 | try { 88 | serviceClientApp(serverSocket.accept()); 89 | } catch (Throwable e) { 90 | error("Error on accept", e); 91 | } 92 | } 93 | } 94 | }).start(); 95 | } 96 | catch (IOException e) { 97 | error("Unable to bind Server Socket", e); 98 | } 99 | } 100 | 101 | String frameworks = "", executablePath = "", arch = ""; 102 | volatile OutputStream clientOutput; 103 | boolean sentProjectPath = false; 104 | 105 | void serviceClientApp(final Socket socket) throws Throwable { 106 | socket.setTcpNoDelay(true); 107 | 108 | final InputStream clientInput = socket.getInputStream(); 109 | clientOutput = socket.getOutputStream(); 110 | sentProjectPath = false; 111 | 112 | // Temporary dorectory to use 113 | final String tmpDir = "/tmp", prefix = tmpDir+"/eval"; 114 | writeString(clientOutput, tmpDir); 115 | 116 | // Sanity check 117 | if (!"bvijkijyhbtrbrebzjbbzcfbbvvq".equals(readString(clientInput))) 118 | return; 119 | 120 | // Not relevant for this version 121 | // Bundle does all the work. 122 | frameworks = readString(clientInput); 123 | arch = readString(clientInput); 124 | executablePath = readString(clientInput); 125 | 126 | new Thread(new Runnable() { 127 | public void run() { 128 | try { 129 | while (true) { 130 | int resp = readInt(clientInput) & 0x7fffffff; 131 | if (resp < InjectionResponse.values().length) 132 | switch (InjectionResponse.values()[resp]) { 133 | case Sign: 134 | String dylib = readString(clientInput); 135 | if (!new File(dylib).exists()) 136 | dylib = prefix+dylib; 137 | else if (!dylib.startsWith(prefix)) 138 | error("Signing exception", new IOException("Invalid path")); 139 | try { 140 | Process process = Runtime.getRuntime().exec(new String[] {"/bin/bash", "-c", 141 | "(export CODESIGN_ALLOCATE=/Applications/Xcode.app" + 142 | "/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; " + 143 | "/usr/bin/codesign --force -s \"-\" \""+dylib+"\")"}); 144 | int status = process.waitFor(); 145 | writeCommand(clientOutput, InjectionCommand.Signed.ordinal(), status == 0 ? "1" : "0"); 146 | } 147 | catch (Exception e) { 148 | error("Signing exception", e); 149 | } 150 | break; 151 | case Error: 152 | alert("Injection error: "+readString(clientInput)); 153 | default: 154 | break; 155 | }; 156 | } 157 | } 158 | catch (Exception e) { 159 | } 160 | finally { 161 | try { 162 | socket.close(); 163 | } 164 | catch (IOException e) { 165 | } 166 | clientOutput = null; 167 | } 168 | } 169 | }).start(); 170 | } 171 | 172 | int injectFile(AnActionEvent event) { 173 | 174 | try { 175 | if (!new File(bundlePath).exists()) 176 | return alert("Please download InjectionIII from the Mac App Store."); 177 | if (clientOutput == null) 178 | return alert("Application not running/connected."); 179 | 180 | Project project = event.getData(PlatformDataKeys.PROJECT); 181 | if (project == null) 182 | alert("Null project"); 183 | if (!sentProjectPath) { 184 | VirtualFile proj = project.getBaseDir(); 185 | String projectPath = proj.getPath() + "/" + proj.getName() + ".xcworkspace"; 186 | if (!new File(projectPath).exists()) 187 | projectPath = proj.getPath() + "/" + proj.getName() + ".xcodeproj"; 188 | writeCommand(clientOutput, InjectionCommand.Connected.ordinal(), projectPath); 189 | sentProjectPath = true; 190 | } 191 | 192 | VirtualFile vf = event.getData(PlatformDataKeys.VIRTUAL_FILE); 193 | if (vf == null) 194 | return 0; 195 | 196 | String selectedFile = vf.getCanonicalPath(); 197 | FileDocumentManager.getInstance().saveAllDocuments(); 198 | writeCommand(clientOutput, InjectionCommand.Inject.ordinal(), selectedFile); 199 | } 200 | catch (Throwable e) { 201 | error("Inject File error", e); 202 | } 203 | 204 | return 0; 205 | } 206 | 207 | // Socket I/O 208 | static int unsign(byte b) { 209 | return (int)b & 0xff; 210 | } 211 | 212 | static int readInt(InputStream s) throws IOException { 213 | byte bytes[] = new byte[4]; 214 | if (s.read(bytes) != bytes.length) 215 | throw new IOException("readInt() EOF"); 216 | return unsign(bytes[0]) + (unsign(bytes[1])<<8) + (unsign(bytes[2])<<16) + (unsign(bytes[3])<<24); 217 | } 218 | 219 | static String readString(InputStream s) throws IOException { 220 | int pathLength = readInt(s); 221 | if (pathLength > 1000000) 222 | pathLength = readInt(s); 223 | byte buffer[] = new byte[pathLength]; 224 | if (s.read(buffer) != pathLength) 225 | alert("Bad path read, pathLength :"+pathLength); 226 | return new String(buffer, 0, pathLength, CHARSET); 227 | } 228 | 229 | static void writeInt(OutputStream s, int i1) throws IOException { 230 | byte bytes[] = new byte[4]; 231 | bytes[0] = (byte) (i1); 232 | bytes[1] = (byte) (i1 >> 8); 233 | bytes[2] = (byte) (i1 >> 16); 234 | bytes[3] = (byte) (i1 >> 24); 235 | s.write(bytes); 236 | s.flush(); 237 | } 238 | 239 | static void writeString(OutputStream s, String path) throws IOException { 240 | byte bytes[] = path.getBytes(CHARSET); 241 | writeInt(s, bytes.length); 242 | s.write(bytes); 243 | s.flush(); 244 | } 245 | 246 | static void writeCommand(OutputStream s, int command, String string) throws IOException { 247 | writeInt(s, command); 248 | if (string != null) 249 | writeString(s, string); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /BAZEL.md: -------------------------------------------------------------------------------- 1 | ### Experimental Bazel build system support (now removed) 2 | 3 | The [binary GitHub releases](https://github.com/johnno1962/InjectionIII/releases), version 4.5.* or above contain an initial implementation of injecting larger apps which have elected to use the [bazel build system](https://bazel.build/). 4 | 5 | In fact, there are two implementations available. A more conservative implementation, searches the Xcode build logs for a line starting `Running "` where `bazel` is invoked and calls this command when a source file is modified. It then looks for object files that have been modified by the build and "injects" then in the way the InjectionIII has up until now. To use this version download one of the binary 4.5+ releases of the InjectionIII app, run it and add the following bundle load code somewhere in your app's initialisation: 6 | 7 | ``` 8 | #if DEBUG 9 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")!.load() 10 | #endif 11 | ``` 12 | When your app starts a file open panel will appear asking you to select the project's root directory which will be used to start a "file watcher" watching for modifications to Swift source files. In theory, if you have used [tulsi](https://github.com/bazelbuild/tulsi) to create your Xcode project, when you save a file, the InjectionIII app will see the `bazel` invocation in the build logs and use it to recompile the project sources and inject the object files that were updated. Note: it's important to the `--linkopt="-Wl,interposable"` either in your Xcode project's build phase that invokes `bazel` or in the relevant BUILD file. 13 | 14 | There is a second less conservative implementation that you should find injects code modifications more quickly. To use this version, quit the InjectionIII app and restart your app to use "standalone" injection. When you save a file, this version (if it finds a WORKSPACE file in a directory somewhere above the source file changed) will only recompile the module of Swift file modified rather than a do full `bazel` rebuild. It then injects object files modified as before. To do this, this version very slightly patches your `bazel` installation to make a link available in /tmp/bazel_ModuleName.params to preserve the parameters file `bazel` passed directly to `swiftc` to incrementally recompile the module in the last build. 15 | 16 | InjectionIII, using the version with or without the app running works best if you don't use "Whole module optimization" otherwise, all object files are regenerated and injection has to resort to heuristics to determine which other objects will be included to cover "shared hidden symbols" resulting in slower iteration times. For more details on the evolution of this feature consult the original [github issue](https://github.com/johnno1962/InjectionIII/issues/388). 17 | 18 | $Date: 2025/03/26 $ 19 | -------------------------------------------------------------------------------- /Bootstrap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2017 John Holdsworth. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Bootstrap/mach_inject_bundle_stub.c: -------------------------------------------------------------------------------- 1 | // mach_inject_bundle_stub.c semver:1.2.0 2 | // Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/mach_inject 5 | // 6 | // Design inspired by SCPatchLoader by Jon Gotow of St. Clair Software: 7 | // http://www.stclairsoft.com 8 | 9 | #include "mach_inject_bundle_stub.h" 10 | #include "mach_inject.h" // for INJECT_ENTRY 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | /************************** 24 | * 25 | * Funky Protos 26 | * 27 | **************************/ 28 | #pragma mark (Funky Protos) 29 | 30 | void 31 | INJECT_ENTRY( 32 | ptrdiff_t codeOffset, 33 | mach_inject_bundle_stub_param *param, 34 | size_t paramSize, 35 | char *dummy_pthread_struc ); 36 | 37 | void* 38 | pthread_entry( 39 | mach_inject_bundle_stub_param *param ); 40 | 41 | 42 | /******************************************************************************* 43 | * 44 | * Implementation 45 | * 46 | *******************************************************************************/ 47 | #pragma mark - 48 | #pragma mark (Implementation) 49 | 50 | void 51 | INJECT_ENTRY( 52 | ptrdiff_t codeOffset, 53 | mach_inject_bundle_stub_param *param, 54 | size_t paramSize, 55 | char *dummy_pthread_struct ) 56 | { 57 | assert( param ); 58 | 59 | #if defined (__i386__) || defined(__x86_64__) 60 | // On intel, per-pthread data is a zone of data that must be allocated. 61 | // if not, all function trying to access per-pthread data (all mig functions for instance) 62 | // will crash. 63 | extern void _pthread_set_self(char*); 64 | _pthread_set_self(dummy_pthread_struct); 65 | #endif 66 | 67 | // fprintf(stderr, "mach_inject_bundle: entered in %s, codeOffset: %td, param: %p, paramSize: %lu\n", 68 | // INJECT_ENTRY_SYMBOL, codeOffset, param, paramSize); 69 | 70 | pthread_attr_t attr; 71 | pthread_attr_init(&attr); 72 | 73 | int policy; 74 | pthread_attr_getschedpolicy( &attr, &policy ); 75 | pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); 76 | pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ); 77 | 78 | struct sched_param sched; 79 | sched.sched_priority = sched_get_priority_max( policy ); 80 | pthread_attr_setschedparam( &attr, &sched ); 81 | 82 | pthread_t thread; 83 | pthread_create( &thread, 84 | &attr, 85 | (void* (*)(void*))((long)pthread_entry + codeOffset), 86 | (void*) param ); 87 | pthread_attr_destroy(&attr); 88 | 89 | thread_suspend(mach_thread_self()); 90 | } 91 | 92 | void* 93 | pthread_entry( 94 | mach_inject_bundle_stub_param *param ) { 95 | 96 | // The following descent into dark magic is brought to you by: 97 | // https://en.wikipedia.org/wiki/Address_space_layout_randomization 98 | // 99 | // As a start, the first page actually loaded into memory is located. 100 | // Shortly after this page will be "libdyld" which is itself a dylib. 101 | // Which page is found using a offset determined in the Helper using "nm" 102 | // in confunction with the first 16 bytes of the dlopen() machine code 103 | // which seems to be shared across tested iOS versions (8.4 -> 10.0.) 104 | // 105 | // Move along, nothing to see here... 106 | 107 | #define VALID_ADDRESS( _addr ) (mincore( _addr, PAGE_SIZE, vec ) == 0 && vec[0] & MINCORE_INCORE) 108 | 109 | char *loadAddress = (char *)0x100000000, vec[1]; 110 | while ( !VALID_ADDRESS( loadAddress ) ) 111 | loadAddress += PAGE_SIZE; 112 | 113 | static unsigned char dlopenInstrux[] = { 114 | 0x55, 0x48, 0x89, 0xe5, 0x41, 0x56, 0x53, 0x41, 115 | 0x89, 0xf6, 0x48, 0x89, 0xfb, 0x48}; 116 | typedef void * (*dlopen_f)(const char * __path, int __mode); 117 | dlopen_f dlopen_ = NULL; 118 | 119 | for ( ; loadAddress < (char *)0x200000000 && VALID_ADDRESS( loadAddress ) ; loadAddress += PAGE_SIZE ) 120 | if (memcmp(loadAddress + param->dlopenPageOffset, dlopenInstrux, sizeof dlopenInstrux) == 0) { 121 | dlopen_ = (dlopen_f)(loadAddress + param->dlopenPageOffset); 122 | break; 123 | } 124 | 125 | const char *error = NULL; 126 | 127 | if (!dlopen_) 128 | error = "Could not locate dlopen()"; 129 | else if ( dlopen_(param->bundleExecutableFileSystemRepresentation, RTLD_NOW) == NULL ) 130 | error = ((const char *(*)(void))(loadAddress + param->dlerrorPageOffset))(); 131 | 132 | if (error) { 133 | int log = open(HELPER_LOGFILE, O_CREAT|O_WRONLY|O_APPEND, 0666); 134 | write(log, error, strlen(error)); 135 | write(log, "\n", 1); 136 | close(log); 137 | } 138 | 139 | return NULL; 140 | } 141 | 142 | -------------------------------------------------------------------------------- /Bootstrap/mach_inject_bundle_stub.h: -------------------------------------------------------------------------------- 1 | // mach_inject_bundle_stub.h semver:1.2.0 2 | // Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/mach_inject 5 | // 6 | // Design inspired by SCPatchLoader by Jon Gotow of St. Clair Software: 7 | // http://www.stclairsoft.com 8 | 9 | #ifndef _mach_inject_bundle_stub_ 10 | #define _mach_inject_bundle_stub_ 11 | 12 | #define HELPER_LOGFILE "/tmp/helper.log" 13 | 14 | typedef struct { 15 | unsigned dlopenPageOffset, dlerrorPageOffset; 16 | char bundleExecutableFileSystemRepresentation[1]; 17 | } mach_inject_bundle_stub_param; 18 | 19 | #endif // _mach_inject_bundle_stub_ 20 | -------------------------------------------------------------------------------- /DDHotKey/DDHotKeyCenter.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.h 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | 13 | //a convenient typedef for the required signature of a hotkey block callback 14 | typedef void (^DDHotKeyTask)(NSEvent*); 15 | 16 | @interface DDHotKey : NSObject 17 | 18 | // creates a new hotkey but does not register it 19 | + (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task; 20 | 21 | @property (nonatomic, assign, readonly) id target; 22 | @property (nonatomic, readonly) SEL action; 23 | @property (nonatomic, strong, readonly) id object; 24 | @property (nonatomic, copy, readonly) DDHotKeyTask task; 25 | 26 | @property (nonatomic, readonly) unsigned short keyCode; 27 | @property (nonatomic, readonly) NSUInteger modifierFlags; 28 | 29 | @end 30 | 31 | #pragma mark - 32 | 33 | @interface DDHotKeyCenter : NSObject 34 | 35 | + (instancetype)sharedHotKeyCenter; 36 | 37 | /** 38 | Register a hotkey. 39 | */ 40 | - (DDHotKey *)registerHotKey:(DDHotKey *)hotKey; 41 | 42 | /** 43 | Register a target/action hotkey. 44 | The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; 45 | Returns the hotkey registered. If registration failed, returns nil. 46 | */ 47 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object; 48 | 49 | /** 50 | Register a block callback hotkey. 51 | The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; 52 | Returns the hotkey registered. If registration failed, returns nil. 53 | */ 54 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task; 55 | 56 | /** 57 | See if a hotkey exists with the specified keycode and modifier flags. 58 | NOTE: this will only check among hotkeys you have explicitly registered with DDHotKeyCenter. This does not check all globally registered hotkeys. 59 | */ 60 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 61 | 62 | /** 63 | Unregister a specific hotkey 64 | */ 65 | - (void)unregisterHotKey:(DDHotKey *)hotKey; 66 | 67 | /** 68 | Unregister all hotkeys 69 | */ 70 | - (void)unregisterAllHotKeys; 71 | 72 | /** 73 | Unregister all hotkeys with a specific target 74 | */ 75 | - (void)unregisterHotKeysWithTarget:(id)target; 76 | 77 | /** 78 | Unregister all hotkeys with a specific target and action 79 | */ 80 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action; 81 | 82 | /** 83 | Unregister a hotkey with a specific keycode and modifier flags 84 | */ 85 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 86 | 87 | /** 88 | Returns a set of currently registered hotkeys 89 | **/ 90 | - (NSSet *)registeredHotKeys; 91 | 92 | @end 93 | 94 | -------------------------------------------------------------------------------- /DDHotKey/DDHotKeyCenter.m: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.m 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | #import "DDHotKeyCenter.h" 15 | #import "DDHotKeyUtilities.h" 16 | 17 | #pragma clang diagnostic push 18 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 19 | 20 | #pragma mark Private Global Declarations 21 | 22 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); 23 | 24 | #pragma mark DDHotKey 25 | 26 | @interface DDHotKey () 27 | 28 | @property (nonatomic, retain) NSValue *hotKeyRef; 29 | @property (nonatomic) UInt32 hotKeyID; 30 | 31 | 32 | @property (nonatomic, assign, setter = _setTarget:) id target; 33 | @property (nonatomic, setter = _setAction:) SEL action; 34 | @property (nonatomic, strong, setter = _setObject:) id object; 35 | @property (nonatomic, copy, setter = _setTask:) DDHotKeyTask task; 36 | 37 | @property (nonatomic, setter = _setKeyCode:) unsigned short keyCode; 38 | @property (nonatomic, setter = _setModifierFlags:) NSUInteger modifierFlags; 39 | 40 | @end 41 | 42 | @implementation DDHotKey 43 | 44 | + (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { 45 | DDHotKey *newHotKey = [[self alloc] init]; 46 | [newHotKey _setTask:task]; 47 | [newHotKey _setKeyCode:keyCode]; 48 | [newHotKey _setModifierFlags:flags]; 49 | return newHotKey; 50 | } 51 | 52 | - (void) dealloc { 53 | [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self]; 54 | } 55 | 56 | - (NSUInteger)hash { 57 | return [self keyCode] ^ [self modifierFlags]; 58 | } 59 | 60 | - (BOOL)isEqual:(id)object { 61 | BOOL equal = NO; 62 | if ([object isKindOfClass:[DDHotKey class]]) { 63 | equal = ([object keyCode] == [self keyCode]); 64 | equal &= ([object modifierFlags] == [self modifierFlags]); 65 | } 66 | return equal; 67 | } 68 | 69 | - (NSString *)description { 70 | NSMutableArray *bits = [NSMutableArray array]; 71 | if ((_modifierFlags & NSControlKeyMask) > 0) { [bits addObject:@"NSControlKeyMask"]; } 72 | if ((_modifierFlags & NSCommandKeyMask) > 0) { [bits addObject:@"NSCommandKeyMask"]; } 73 | if ((_modifierFlags & NSShiftKeyMask) > 0) { [bits addObject:@"NSShiftKeyMask"]; } 74 | if ((_modifierFlags & NSAlternateKeyMask) > 0) { [bits addObject:@"NSAlternateKeyMask"]; } 75 | 76 | NSString *flags = [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; 77 | NSString *invokes = @"(block)"; 78 | if ([self target] != nil && [self action] != nil) { 79 | invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; 80 | } 81 | return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; 82 | } 83 | 84 | - (void)invokeWithEvent:(NSEvent *)event { 85 | if (_target != nil && _action != nil && [_target respondsToSelector:_action]) { 86 | #pragma clang diagnostic push 87 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 88 | [_target performSelector:_action withObject:event withObject:_object]; 89 | #pragma clang diagnostic pop 90 | } else if (_task != nil) { 91 | _task(event); 92 | } 93 | } 94 | 95 | @end 96 | 97 | #pragma mark DDHotKeyCenter 98 | 99 | static DDHotKeyCenter *sharedHotKeyCenter = nil; 100 | 101 | @implementation DDHotKeyCenter { 102 | NSMutableSet *_registeredHotKeys; 103 | UInt32 _nextHotKeyID; 104 | } 105 | 106 | + (instancetype)sharedHotKeyCenter { 107 | static dispatch_once_t onceToken; 108 | dispatch_once(&onceToken, ^{ 109 | sharedHotKeyCenter = [super allocWithZone:nil]; 110 | sharedHotKeyCenter = [sharedHotKeyCenter init]; 111 | 112 | EventTypeSpec eventSpec; 113 | eventSpec.eventClass = kEventClassKeyboard; 114 | eventSpec.eventKind = kEventHotKeyReleased; 115 | InstallApplicationEventHandler(&dd_hotKeyHandler, 1, &eventSpec, NULL, NULL); 116 | }); 117 | return sharedHotKeyCenter; 118 | } 119 | 120 | + (id)allocWithZone:(NSZone *)zone { 121 | return sharedHotKeyCenter; 122 | } 123 | 124 | - (id)init { 125 | if (self != sharedHotKeyCenter) { return sharedHotKeyCenter; } 126 | 127 | self = [super init]; 128 | if (self) { 129 | _registeredHotKeys = [[NSMutableSet alloc] init]; 130 | _nextHotKeyID = 1; 131 | } 132 | return self; 133 | } 134 | 135 | - (NSSet *)hotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { 136 | NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 137 | return matcher(evaluatedObject); 138 | }]; 139 | return [_registeredHotKeys filteredSetUsingPredicate:predicate]; 140 | } 141 | 142 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 143 | return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { 144 | return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; 145 | }].count > 0; 146 | } 147 | 148 | - (DDHotKey *)_registerHotKey:(DDHotKey *)hotKey { 149 | if ([_registeredHotKeys containsObject:hotKey]) { 150 | return hotKey; 151 | } 152 | 153 | EventHotKeyID keyID; 154 | keyID.signature = 'htk1'; 155 | keyID.id = _nextHotKeyID; 156 | 157 | EventHotKeyRef carbonHotKey; 158 | UInt32 flags = DDCarbonModifierFlagsFromCocoaModifiers([hotKey modifierFlags]); 159 | OSStatus err = RegisterEventHotKey([hotKey keyCode], flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); 160 | 161 | //error registering hot key 162 | if (err != 0) { return nil; } 163 | 164 | NSValue *refValue = [NSValue valueWithPointer:carbonHotKey]; 165 | [hotKey setHotKeyRef:refValue]; 166 | [hotKey setHotKeyID:_nextHotKeyID]; 167 | 168 | _nextHotKeyID++; 169 | [_registeredHotKeys addObject:hotKey]; 170 | 171 | return hotKey; 172 | } 173 | 174 | - (DDHotKey *)registerHotKey:(DDHotKey *)hotKey { 175 | return [self _registerHotKey:hotKey]; 176 | } 177 | 178 | - (void)unregisterHotKey:(DDHotKey *)hotKey { 179 | NSValue *hotKeyRef = [hotKey hotKeyRef]; 180 | if (hotKeyRef) { 181 | EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; 182 | UnregisterEventHotKey(carbonHotKey); 183 | [hotKey setHotKeyRef:nil]; 184 | } 185 | 186 | [_registeredHotKeys removeObject:hotKey]; 187 | } 188 | 189 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { 190 | //we can't add a new hotkey if something already has this combo 191 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return nil; } 192 | 193 | DDHotKey *newHotKey = [[DDHotKey alloc] init]; 194 | [newHotKey _setTask:task]; 195 | [newHotKey _setKeyCode:keyCode]; 196 | [newHotKey _setModifierFlags:flags]; 197 | 198 | return [self _registerHotKey:newHotKey]; 199 | } 200 | 201 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object { 202 | //we can't add a new hotkey if something already has this combo 203 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return nil; } 204 | 205 | //build the hotkey object: 206 | DDHotKey *newHotKey = [[DDHotKey alloc] init]; 207 | [newHotKey _setTarget:target]; 208 | [newHotKey _setAction:action]; 209 | [newHotKey _setObject:object]; 210 | [newHotKey _setKeyCode:keyCode]; 211 | [newHotKey _setModifierFlags:flags]; 212 | return [self _registerHotKey:newHotKey]; 213 | } 214 | 215 | - (void)unregisterHotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { 216 | //explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic 217 | @autoreleasepool { 218 | NSSet *matches = [self hotKeysMatching:matcher]; 219 | for (DDHotKey *hotKey in matches) { 220 | [self unregisterHotKey:hotKey]; 221 | } 222 | } 223 | } 224 | 225 | - (void)unregisterHotKeysWithTarget:(id)target { 226 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 227 | return hotkey.target == target; 228 | }]; 229 | } 230 | 231 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action { 232 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 233 | return hotkey.target == target && sel_isEqual(hotkey.action, action); 234 | }]; 235 | } 236 | 237 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 238 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 239 | return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; 240 | }]; 241 | } 242 | 243 | - (void)unregisterAllHotKeys { 244 | NSSet *keys = [_registeredHotKeys copy]; 245 | for (DDHotKey *key in keys) { 246 | [self unregisterHotKey:key]; 247 | } 248 | } 249 | 250 | - (NSSet *)registeredHotKeys { 251 | return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { 252 | return hotkey.hotKeyRef != NULL; 253 | }]; 254 | } 255 | 256 | @end 257 | 258 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { 259 | @autoreleasepool { 260 | EventHotKeyID hotKeyID; 261 | GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); 262 | 263 | UInt32 keyID = hotKeyID.id; 264 | 265 | NSSet *matchingHotKeys = [[DDHotKeyCenter sharedHotKeyCenter] hotKeysMatching:^BOOL(DDHotKey *hotkey) { 266 | return hotkey.hotKeyID == keyID; 267 | }]; 268 | if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } 269 | DDHotKey *matchingHotKey = [matchingHotKeys anyObject]; 270 | 271 | NSEvent *event = [NSEvent eventWithEventRef:theEvent]; 272 | NSEvent *keyEvent = [NSEvent keyEventWithType:NSKeyUp 273 | location:[event locationInWindow] 274 | modifierFlags:[event modifierFlags] 275 | timestamp:[event timestamp] 276 | windowNumber:-1 277 | context:nil 278 | characters:@"" 279 | charactersIgnoringModifiers:@"" 280 | isARepeat:NO 281 | keyCode:[matchingHotKey keyCode]]; 282 | 283 | [matchingHotKey invokeWithEvent:keyEvent]; 284 | } 285 | 286 | return noErr; 287 | } 288 | -------------------------------------------------------------------------------- /DDHotKey/DDHotKeyUtilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyUtilities.h 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | 13 | extern NSString *DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers); 14 | extern UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags); 15 | -------------------------------------------------------------------------------- /DDHotKey/DDHotKeyUtilities.m: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyUtilities.m 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import "DDHotKeyUtilities.h" 12 | #import 13 | #import 14 | 15 | #pragma clang diagnostic push 16 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 17 | 18 | static NSDictionary *_DDKeyCodeToCharacterMap(void); 19 | static NSDictionary *_DDKeyCodeToCharacterMap(void) { 20 | static NSDictionary *keyCodeMap = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | keyCodeMap = @{ 24 | @(kVK_Return) : @"↩", 25 | @(kVK_Tab) : @"⇥", 26 | @(kVK_Space) : @"⎵", 27 | @(kVK_Delete) : @"⌫", 28 | @(kVK_Escape) : @"⎋", 29 | @(kVK_Command) : @"⌘", 30 | @(kVK_Shift) : @"⇧", 31 | @(kVK_CapsLock) : @"⇪", 32 | @(kVK_Option) : @"⌥", 33 | @(kVK_Control) : @"⌃", 34 | @(kVK_RightShift) : @"⇧", 35 | @(kVK_RightOption) : @"⌥", 36 | @(kVK_RightControl) : @"⌃", 37 | @(kVK_VolumeUp) : @"🔊", 38 | @(kVK_VolumeDown) : @"🔈", 39 | @(kVK_Mute) : @"🔇", 40 | @(kVK_Function) : @"\u2318", 41 | @(kVK_F1) : @"F1", 42 | @(kVK_F2) : @"F2", 43 | @(kVK_F3) : @"F3", 44 | @(kVK_F4) : @"F4", 45 | @(kVK_F5) : @"F5", 46 | @(kVK_F6) : @"F6", 47 | @(kVK_F7) : @"F7", 48 | @(kVK_F8) : @"F8", 49 | @(kVK_F9) : @"F9", 50 | @(kVK_F10) : @"F10", 51 | @(kVK_F11) : @"F11", 52 | @(kVK_F12) : @"F12", 53 | @(kVK_F13) : @"F13", 54 | @(kVK_F14) : @"F14", 55 | @(kVK_F15) : @"F15", 56 | @(kVK_F16) : @"F16", 57 | @(kVK_F17) : @"F17", 58 | @(kVK_F18) : @"F18", 59 | @(kVK_F19) : @"F19", 60 | @(kVK_F20) : @"F20", 61 | // @(kVK_Help) : @"", 62 | @(kVK_ForwardDelete) : @"⌦", 63 | @(kVK_Home) : @"↖", 64 | @(kVK_End) : @"↘", 65 | @(kVK_PageUp) : @"⇞", 66 | @(kVK_PageDown) : @"⇟", 67 | @(kVK_LeftArrow) : @"←", 68 | @(kVK_RightArrow) : @"→", 69 | @(kVK_DownArrow) : @"↓", 70 | @(kVK_UpArrow) : @"↑", 71 | }; 72 | }); 73 | return keyCodeMap; 74 | } 75 | 76 | NSString *DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers) { 77 | NSMutableString *final = [NSMutableString stringWithString:@""]; 78 | NSDictionary *characterMap = _DDKeyCodeToCharacterMap(); 79 | 80 | if (modifiers & NSControlKeyMask) { 81 | [final appendString:[characterMap objectForKey:@(kVK_Control)]]; 82 | } 83 | if (modifiers & NSAlternateKeyMask) { 84 | [final appendString:[characterMap objectForKey:@(kVK_Option)]]; 85 | } 86 | if (modifiers & NSShiftKeyMask) { 87 | [final appendString:[characterMap objectForKey:@(kVK_Shift)]]; 88 | } 89 | if (modifiers & NSCommandKeyMask) { 90 | [final appendString:[characterMap objectForKey:@(kVK_Command)]]; 91 | } 92 | 93 | if (keyCode == kVK_Control || keyCode == kVK_Option || keyCode == kVK_Shift || keyCode == kVK_Command) { 94 | return final; 95 | } 96 | 97 | NSString *mapped = [characterMap objectForKey:@(keyCode)]; 98 | if (mapped != nil) { 99 | [final appendString:mapped]; 100 | } else { 101 | 102 | TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); 103 | CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 104 | 105 | // Fix crash using non-unicode layouts, such as Chinese or Japanese. 106 | if (!uchr) { 107 | CFRelease(currentKeyboard); 108 | currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); 109 | uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 110 | } 111 | 112 | const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); 113 | 114 | if (keyboardLayout) { 115 | UInt32 deadKeyState = 0; 116 | UniCharCount maxStringLength = 255; 117 | UniCharCount actualStringLength = 0; 118 | UniChar unicodeString[maxStringLength]; 119 | 120 | UInt32 keyModifiers = DDCarbonModifierFlagsFromCocoaModifiers(modifiers); 121 | 122 | OSStatus status = UCKeyTranslate(keyboardLayout, 123 | keyCode, kUCKeyActionDown, keyModifiers, 124 | LMGetKbdType(), 0, 125 | &deadKeyState, 126 | maxStringLength, 127 | &actualStringLength, unicodeString); 128 | 129 | if (actualStringLength > 0 && status == noErr) { 130 | NSString *characterString = [NSString stringWithCharacters:unicodeString length:(NSUInteger)actualStringLength]; 131 | 132 | [final appendString:characterString]; 133 | } 134 | } 135 | } 136 | 137 | return final; 138 | } 139 | 140 | UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags) { 141 | UInt32 newFlags = 0; 142 | if ((flags & NSControlKeyMask) > 0) { newFlags |= controlKey; } 143 | if ((flags & NSCommandKeyMask) > 0) { newFlags |= cmdKey; } 144 | if ((flags & NSShiftKeyMask) > 0) { newFlags |= shiftKey; } 145 | if ((flags & NSAlternateKeyMask) > 0) { newFlags |= optionKey; } 146 | if ((flags & NSAlphaShiftKeyMask) > 0) { newFlags |= alphaLock; } 147 | return newFlags; 148 | } 149 | -------------------------------------------------------------------------------- /DDHotKey/README.markdown: -------------------------------------------------------------------------------- 1 | # DDHotKey 2 | 3 | Copyright © Dave DeLong 4 | 5 | ## About 6 | 7 | DDHotKey is an easy-to-use Cocoa class for registering an application to respond to system key events, or "hotkeys". 8 | 9 | A global hotkey is a key combination that always executes a specific action, regardless of which app is frontmost. For example, the Mac OS X default hotkey of "command-space" shows the Spotlight search bar, even if Finder is not the frontmost application. 10 | 11 | ## License 12 | 13 | The license for this framework is included in every source file, and is repoduced in its entirety here: 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 16 | 17 | ## Usage 18 | 19 | ### Including DDHotKey in your project 20 | 21 | You will need to copy these six files into your project: 22 | 23 | - DDHotKeyCenter.h 24 | - DDHotKeyCenter.m 25 | - DDHotKeyUtilities.h 26 | - DDHotKeyUtilities.m 27 | - DDHotKeyTextField.h 28 | - DDHotKeyTextField.m 29 | 30 | Your application will need to link against `Carbon.framework`, and you will need to compile your application with the Clang compiler. DDHotKey has been tested with Xcode 5 on OS X Mavericks. No attempt has been made to preserve backwards compatibility. 31 | 32 | ### Using DDHotKey in your code 33 | 34 | When you wish to create a hotkey, you'll need to do so via the `DDHotKeyCenter` singleton. 35 | 36 | You can register a hotkey in one of two ways: via a target/action mechanism, or with a block. The target/action mechanism can take a single extra "object" parameter, which it will pass into the action when the hotkey is fired. Only the `object` parameter is retained by the `DDHotKeyCenter`. In addition, an `NSEvent` object is passed, which contains information regarding the hotkey event (such as the location, the keyCode, the modifierFlags, etc). 37 | 38 | Hotkey actions must have one of two method signatures (the actual selector is irrelevant): 39 | 40 | //a method with a single NSEvent parameter 41 | - (void)hotkeyAction:(NSEvent*)hotKeyEvent; 42 | 43 | OR 44 | 45 | //a method with an NSEvent parameter and an object parameter 46 | - (void)hotkeyAction:(NSEvent*)hotKeyEvent withObject:(id)anObject; 47 | 48 | The other way to register a hotkey is with a block callback. The block must have the following signature: 49 | 50 | void (^)(NSEvent *); 51 | 52 | `DDHotKeyCenter.h` contains a typedef statement to typedef this signature as a `DDHotKeyTask`, for convenience. 53 | 54 | Any hotkey that you have registered via `DDHotKeyCenter` can be unregistered based on its target, its target and action, or its keycode and modifier flags. 55 | 56 | DDHotKey also includes a rudimentary `DDHotKeyTextField`, which is an `NSTextField` subclass that simplifies the process of creating a key combination. Simply drop an `NSTextField` into your xib and change its class to `DDHotKeyTextField`. Programmatically, you'll get an NSTextField into which you can type arbitrary key combinations. You access the resulting combination via the textfield's `hotKey` property. 57 | -------------------------------------------------------------------------------- /EvalApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EvalApp 4 | // 5 | // Created by John Holdsworth on 04/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet weak var textField: NSTextField! 16 | @IBOutlet weak var textView: NSTextView! 17 | @IBOutlet weak var closureText: NSTextField! 18 | 19 | @IBAction func performEval(_: Any) { 20 | textView.string = swiftEvalString(contents: textField.stringValue) 21 | } 22 | 23 | @IBAction func closureEval(_: Any) { 24 | _ = swiftEval(code: closureText.stringValue+"()") 25 | } 26 | 27 | @objc func injected() { 28 | print("I've been injected!") 29 | } 30 | 31 | func applicationDidFinishLaunching(_ aNotification: Notification) { 32 | // Insert code here to initialize your application 33 | SwiftEval.instance.evalError = { 34 | let err = $0 35 | if !err.hasPrefix("💉 Compiling ") { 36 | DispatchQueue.main.async { 37 | self.textView.string = err 38 | } 39 | } 40 | return NSError(domain: "SwiftEval", code: -1, userInfo: [NSLocalizedDescriptionKey: err]) 41 | } 42 | } 43 | 44 | func applicationWillTerminate(_ aNotification: Notification) { 45 | // Insert code here to tear down your application 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /EvalApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /EvalApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EvalApp/EvalApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.disable-library-validation 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EvalApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | App.icns 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 John Holdsworth. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Helper/Helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.h 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | enum SMHelperError: mach_error_t { 13 | SMHelperErrorsPayload = -40001, 14 | SMHelperErrorsNoSim = -40002, 15 | SMHelperErrorsNoNm = -40003, 16 | SMHelperErrorsNoApp = -40004, 17 | SMHelperErrors32Bits = 4 18 | }; 19 | 20 | @interface Helper : NSObject 21 | 22 | - (mach_error_t)inject:(NSString *)appPath bundle:(NSString *)payload client:(const char *)client 23 | dlopenPageOffset: (unsigned)dlopenPageOffset dlerrorPageOffset: (unsigned)dlerrorPageOffset; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Helper/Helper.m: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.m 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "Helper.h" 10 | #import "mach_inject.h" 11 | #import "mach_inject_bundle_stub.h" 12 | 13 | #include 14 | 15 | static FILE *logger; 16 | 17 | @implementation Helper 18 | 19 | - (NSString *)projectRoot:(const char *)sourceFile { 20 | return [NSString stringWithCString:sourceFile encoding:NSUTF8StringEncoding] 21 | .stringByDeletingLastPathComponent.stringByDeletingLastPathComponent; 22 | } 23 | 24 | - (mach_error_t)inject:(NSString *)appPath bundle:(NSString *)payload client:(const char *)client 25 | dlopenPageOffset: (unsigned)dlopenPageOffset dlerrorPageOffset: (unsigned)dlerrorPageOffset { 26 | assert([[self projectRoot:client] isEqualToString:[self projectRoot:__FILE__]]); 27 | 28 | logger = fopen(HELPER_LOGFILE, "w"); 29 | setvbuf(logger, NULL, _IONBF, 0); 30 | fchmod(fileno(logger), 0666); 31 | 32 | NSBundle *appBundle = [NSBundle bundleWithPath:appPath]; 33 | assert(appBundle && "App Bundle"); 34 | 35 | NSURL *boostrapURL = [appBundle URLForResource:@"Bootstrap" withExtension:@"bundle"]; 36 | CFBundleRef bootstrapBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)boostrapURL); 37 | void *bootstrapEntry = CFBundleGetFunctionPointerForName(bootstrapBundle, CFSTR( INJECT_ENTRY_SYMBOL )); 38 | assert(bootstrapEntry && "Bootstrap Entry"); 39 | 40 | fprintf( logger, "bootstrapEntry: %p\n", bootstrapEntry ); 41 | 42 | NSBundle *payloadBundle = [NSBundle bundleWithPath:payload]; 43 | fprintf( logger, "payloadBundle: %p\n", payloadBundle ); 44 | if (!payloadBundle) { 45 | fprintf( logger, "Could not init payload bundle: %s\n", [payload UTF8String] ); 46 | return SMHelperErrorsPayload; 47 | } 48 | 49 | const char *payloadPath = payloadBundle.executablePath.fileSystemRepresentation; 50 | assert(payloadPath && "Payload Path"); 51 | 52 | fprintf( logger, "payloadPath: %s\n", payloadPath ); 53 | 54 | size_t paramSize = sizeof( mach_inject_bundle_stub_param ) + strlen( payloadPath ); 55 | mach_inject_bundle_stub_param *param = malloc( paramSize ); 56 | 57 | param->dlopenPageOffset = dlopenPageOffset; 58 | param->dlerrorPageOffset = dlerrorPageOffset; 59 | strcpy( param->bundleExecutableFileSystemRepresentation, payloadPath ); 60 | 61 | char pathBuff[PROC_PIDPATHINFO_MAXSIZE]; 62 | memset(pathBuff, 0, sizeof pathBuff); 63 | 64 | pid_t pid = [self pidContaining:"/usr/libexec/MobileGestaltHelper" returning:pathBuff]; 65 | fprintf( logger, "pathBuff: %d %s\n", pid, pathBuff ); 66 | if( pid <= 0 ) { 67 | fprintf( logger, "Simulator does not seem to be running\n" ); 68 | return SMHelperErrorsNoSim; 69 | } 70 | 71 | NSString *simPath = [NSString stringWithUTF8String:pathBuff]; 72 | NSString *dyldPath = [[simPath.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent 73 | stringByAppendingPathComponent:@"lib/system/libdyld.dylib"] 74 | stringByReplacingOccurrencesOfString:@"'" withString:@""]; 75 | 76 | fprintf( logger, "dyldPath: %s\n", [dyldPath UTF8String] ); 77 | 78 | NSString *output = [self run:[NSString stringWithFormat:@"nm '%@' | grep ' _dlopen'", dyldPath]]; 79 | [[NSScanner scannerWithString:output] scanHexInt:¶m->dlopenPageOffset]; 80 | 81 | output = [self run:[NSString stringWithFormat:@"nm '%@' | grep ' _dlerror'", dyldPath]]; 82 | [[NSScanner scannerWithString:output] scanHexInt:¶m->dlerrorPageOffset]; 83 | 84 | fprintf( logger, "dlopen() offset: 0x%x, dlerror() offset: 0x%x\n", param->dlopenPageOffset, param->dlerrorPageOffset ); 85 | if( !param->dlopenPageOffset || !param->dlerrorPageOffset ) { 86 | fprintf( logger, "Could not locate locate dlopen() offset, is xcode-select correct\n" ); 87 | return SMHelperErrorsNoNm; 88 | } 89 | 90 | pid = [self pidContaining:"/data/Containers/Bundle/Application/" returning:NULL]; 91 | if( pid <= 0 ) { 92 | fprintf( logger, "Could not locate app running in simulator\n" ); 93 | return SMHelperErrorsNoApp; 94 | } 95 | 96 | fclose( logger ); 97 | 98 | // vm_write() Bootstrap.bundle into target process and execute to load payload 99 | mach_error_t err = mach_inject( bootstrapEntry, param, paramSize, pid, 0 ); 100 | 101 | CFRelease( bootstrapBundle ); 102 | free( param ); 103 | return err; 104 | } 105 | 106 | - (pid_t)pidContaining:(const char *)text returning:(char *)returning { 107 | int procCnt = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); 108 | pid_t pids[65536]; 109 | memset(pids, 0, sizeof pids); 110 | proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); 111 | 112 | char curPath[PROC_PIDPATHINFO_MAXSIZE]; 113 | memset(curPath, 0, sizeof curPath); 114 | for (int i = 0; i < procCnt; i++) { 115 | proc_pidpath(pids[i], curPath, sizeof curPath); 116 | if ( strstr(curPath, text) != NULL ) { 117 | if (returning) 118 | strcpy( returning, curPath ); 119 | return pids[i]; 120 | } 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | - (NSString *)run:(NSString *)command { 127 | NSTask *task = [NSTask new]; 128 | NSPipe *pipe = [NSPipe new]; 129 | task.launchPath = @"/bin/bash"; 130 | task.arguments = @[@"-c", command]; 131 | task.standardOutput = pipe.fileHandleForWriting; 132 | [task launch]; 133 | [pipe.fileHandleForWriting closeFile]; 134 | [task waitUntilExit]; 135 | NSData *output = pipe.fileHandleForReading.readDataToEndOfFile; 136 | return [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding]; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /Helper/Info-Launchd.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.johnholdsworth.InjectionIII.Helper 7 | MachServices 8 | 9 | com.johnholdsworth.InjectionIII.Helper.mach 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Helper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.johnholdsworth.InjectionIII.Helper 7 | CFBundleInfoDictionaryVersion 8 | 6.0 9 | CFBundleShortVersionString 10 | 1.0 11 | CFBundleSignature 12 | ???? 13 | CFBundleVersion 14 | 1 15 | NSHumanReadableCopyright 16 | Copyright © 2016 John Holdsworth. All rights reserved. 17 | NSPrincipalClass 18 | 19 | SMAuthorizedClients 20 | 21 | identifier com.johnholdsworth.InjectionIII 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Helper/mach_inject.c: -------------------------------------------------------------------------------- 1 | // mach_inject.c semver:1.2.0 2 | // Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/mach_inject 5 | 6 | #include "mach_inject.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include // for malloc() 15 | #include // for printf() 16 | #include // for fat structure decoding 17 | #include // to know which is local arch 18 | #include // for open/close 19 | // for mmap() 20 | #include 21 | #include 22 | 23 | #ifndef COMPILE_TIME_ASSERT//( exp ) 24 | #define COMPILE_TIME_ASSERT( exp ) { switch (0) { case 0: case (exp):; } } 25 | #endif 26 | #define ASSERT_CAST( CAST_TO, CAST_FROM ) \ 27 | COMPILE_TIME_ASSERT( sizeof(CAST_TO)==sizeof(CAST_FROM) ) 28 | 29 | #if defined(__i386__) 30 | void* fixedUpImageFromImage ( 31 | const void *image, 32 | unsigned long imageSize, 33 | unsigned int jumpTableOffset, 34 | unsigned int jumpTableSize, 35 | ptrdiff_t fixUpOffset); 36 | #endif /* __i386__ */ 37 | 38 | #include 39 | #define MACH_ERROR(msg, err) { if(err != err_none) mach_error(msg, err); } 40 | 41 | /******************************************************************************* 42 | * 43 | * Interface 44 | * 45 | *******************************************************************************/ 46 | #pragma mark - 47 | #pragma mark (Interface) 48 | 49 | extern int close(int); 50 | 51 | mach_error_t 52 | mach_inject( 53 | const mach_inject_entry threadEntry, 54 | const void *paramBlock, 55 | size_t paramSize, 56 | pid_t targetProcess, 57 | vm_size_t stackSize ) 58 | { 59 | assert( threadEntry ); 60 | assert( targetProcess > 0 ); 61 | assert( stackSize == 0 || stackSize > 1024 ); 62 | 63 | // Find the image. 64 | const void *image; 65 | unsigned long imageSize; 66 | unsigned int jumpTableOffset; 67 | unsigned int jumpTableSize; 68 | mach_error_t err = machImageForPointer( threadEntry, &image, &imageSize, &jumpTableOffset, &jumpTableSize ); 69 | //fprintf(stderr, "mach_inject: found threadEntry image at: %p with size: %lu\n", image, imageSize); 70 | 71 | // Initialize stackSize to default if requested. 72 | if( stackSize == 0 ) 73 | /** @bug 74 | We only want an 8K default, fix the plop-in-the-middle code below. 75 | */ 76 | stackSize = 16 * 1024; 77 | 78 | // Convert PID to Mach Task ref. 79 | mach_port_t remoteTask = 0; 80 | if( !err ) { 81 | err = task_for_pid( mach_task_self(), targetProcess, &remoteTask ); 82 | #if defined(__i386__) || defined(__x86_64__) 83 | if (err == 5) fprintf(stderr, "Could not access task for pid %d. You probably need to add user to procmod group\n", targetProcess); 84 | #endif 85 | } 86 | 87 | /** @todo 88 | Would be nice to just allocate one block for both the remote stack 89 | *and* the remoteCode (including the parameter data block once that's 90 | written. 91 | */ 92 | 93 | // Allocate the remoteStack. 94 | vm_address_t remoteStack = (vm_address_t)NULL; 95 | if( !err ) 96 | err = vm_allocate( remoteTask, &remoteStack, stackSize, 1 ); 97 | 98 | 99 | // Allocate the code. 100 | vm_address_t remoteCode = (vm_address_t)NULL; 101 | if( !err ) 102 | err = vm_allocate( remoteTask, &remoteCode, imageSize, 1 ); 103 | if( !err ) 104 | err = vm_protect(remoteTask, remoteCode, imageSize, 0, VM_PROT_EXECUTE | VM_PROT_WRITE | VM_PROT_READ); 105 | if( !err ) { 106 | ASSERT_CAST( pointer_t, image ); 107 | #if defined (__ppc__) || defined (__ppc64__) || defined(__x86_64__) 108 | err = vm_write( remoteTask, remoteCode, (pointer_t) image, (mach_msg_type_number_t)imageSize ); 109 | #elif defined (__i386__) 110 | // on x86, jump table use relative jump instructions (jmp), which means 111 | // the offset needs to be corrected. We thus copy the image and fix the offset by hand. 112 | ptrdiff_t fixUpOffset = (ptrdiff_t) (image - remoteCode); 113 | void * fixedUpImage = fixedUpImageFromImage(image, imageSize, jumpTableOffset, jumpTableSize, fixUpOffset); 114 | err = vm_write( remoteTask, remoteCode, (pointer_t) fixedUpImage, imageSize ); 115 | free(fixedUpImage); 116 | #endif 117 | } 118 | 119 | // Allocate the paramBlock if specified. 120 | vm_address_t remoteParamBlock = (vm_address_t)NULL; 121 | if( !err && paramBlock != NULL && paramSize ) { 122 | err = vm_allocate( remoteTask, &remoteParamBlock, paramSize, 1 ); 123 | if( !err ) { 124 | ASSERT_CAST( pointer_t, paramBlock ); 125 | err = vm_write( remoteTask, remoteParamBlock, 126 | (pointer_t) paramBlock, (mach_msg_type_number_t)paramSize ); 127 | } 128 | } 129 | 130 | // Calculate offsets. 131 | ptrdiff_t threadEntryOffset = 0, imageOffset = 0; 132 | if( !err ) { 133 | //assert( (void*)threadEntry >= image && (void*)threadEntry <= (image+imageSize) ); 134 | ASSERT_CAST( void*, threadEntry ); 135 | threadEntryOffset = ((void*) threadEntry) - image; 136 | 137 | #if defined(__x86_64__) 138 | imageOffset = 0; // RIP-relative addressing 139 | #else 140 | //ASSERT_CAST( void*, remoteCode ); 141 | //imageOffset = ((void*) remoteCode) - image; 142 | // WARNING: See bug https://github.com/rentzsch/mach_star/issues/11 . Not sure about this. 143 | imageOffset = 0; 144 | #endif 145 | } 146 | 147 | // Allocate the thread. 148 | thread_act_t remoteThread; 149 | #if defined (__ppc__) || defined (__ppc64__) 150 | if( !err ) { 151 | ppc_thread_state_t remoteThreadState; 152 | 153 | /** @bug 154 | Stack math should be more sophisticated than this (ala redzone). 155 | */ 156 | remoteStack += stackSize / 2; 157 | 158 | bzero( &remoteThreadState, sizeof(remoteThreadState) ); 159 | 160 | ASSERT_CAST( unsigned int, remoteCode ); 161 | remoteThreadState.__srr0 = (unsigned int) remoteCode; 162 | remoteThreadState.__srr0 += threadEntryOffset; 163 | assert( remoteThreadState.__srr0 < (remoteCode + imageSize) ); 164 | 165 | ASSERT_CAST( unsigned int, remoteStack ); 166 | remoteThreadState.__r1 = (unsigned int) remoteStack; 167 | 168 | ASSERT_CAST( unsigned int, imageOffset ); 169 | remoteThreadState.__r3 = (unsigned int) imageOffset; 170 | 171 | ASSERT_CAST( unsigned int, remoteParamBlock ); 172 | remoteThreadState.__r4 = (unsigned int) remoteParamBlock; 173 | 174 | ASSERT_CAST( unsigned int, paramSize ); 175 | remoteThreadState.__r5 = (unsigned int) paramSize; 176 | 177 | ASSERT_CAST( unsigned int, 0xDEADBEEF ); 178 | remoteThreadState.__lr = (unsigned int) 0xDEADBEEF; 179 | 180 | #if 0 181 | printf( "remoteCode start: %p\n", (void*) remoteCode ); 182 | printf( "remoteCode size: %ld\n", imageSize ); 183 | printf( "remoteCode pc: %p\n", (void*) remoteThreadState.srr0 ); 184 | printf( "remoteCode end: %p\n", 185 | (void*) (((char*)remoteCode)+imageSize) ); 186 | fflush(0); 187 | #endif 188 | 189 | err = thread_create_running( remoteTask, PPC_THREAD_STATE, 190 | (thread_state_t) &remoteThreadState, PPC_THREAD_STATE_COUNT, 191 | &remoteThread ); 192 | } 193 | #elif defined (__i386__) 194 | if( !err ) { 195 | 196 | i386_thread_state_t remoteThreadState; 197 | bzero( &remoteThreadState, sizeof(remoteThreadState) ); 198 | 199 | vm_address_t dummy_thread_struct = remoteStack; 200 | remoteStack += (stackSize / 2); // this is the real stack 201 | // (*) increase the stack, since we're simulating a CALL instruction, which normally pushes return address on the stack 202 | remoteStack -= 4; 203 | 204 | #define PARAM_COUNT 4 205 | #define STACK_CONTENTS_SIZE ((1+PARAM_COUNT) * sizeof(unsigned int)) 206 | unsigned int stackContents[1 + PARAM_COUNT]; // 1 for the return address and 1 for each param 207 | // first entry is return address (see above *) 208 | stackContents[0] = 0xDEADBEEF; // invalid return address. 209 | // then we push function parameters one by one. 210 | stackContents[1] = imageOffset; 211 | stackContents[2] = remoteParamBlock; 212 | stackContents[3] = paramSize; 213 | // We use the remote stack we allocated as the fake thread struct. We should probably use a dedicated memory zone. 214 | // We don't fill it with 0, vm_allocate did it for us 215 | stackContents[4] = dummy_thread_struct; 216 | 217 | // push stackContents 218 | err = vm_write( remoteTask, remoteStack, 219 | (pointer_t) stackContents, STACK_CONTENTS_SIZE); 220 | 221 | // set remote Program Counter 222 | remoteThreadState.__eip = (unsigned int) (remoteCode); 223 | remoteThreadState.__eip += threadEntryOffset; 224 | 225 | // set remote Stack Pointer 226 | ASSERT_CAST( unsigned int, remoteStack ); 227 | remoteThreadState.__esp = (unsigned int) remoteStack; 228 | 229 | // create thread and launch it 230 | err = thread_create_running( remoteTask, i386_THREAD_STATE, 231 | (thread_state_t) &remoteThreadState, i386_THREAD_STATE_COUNT, 232 | &remoteThread ); 233 | } 234 | #elif defined(__x86_64__) 235 | if( !err ) { 236 | 237 | x86_thread_state64_t remoteThreadState; 238 | bzero( &remoteThreadState, sizeof(remoteThreadState) ); 239 | 240 | vm_address_t dummy_thread_struct = remoteStack; 241 | remoteStack += (stackSize / 2); // this is the real stack 242 | // (*) increase the stack, since we're simulating a CALL instruction, which normally pushes return address on the stack 243 | remoteStack -= 8; 244 | 245 | #define PARAM_COUNT 0 246 | #define STACK_CONTENTS_SIZE ((1+PARAM_COUNT) * sizeof(unsigned long long)) 247 | unsigned long long stackContents[1 + PARAM_COUNT]; // 1 for the return address and 1 for each param 248 | // first entry is return address (see above *) 249 | stackContents[0] = 0x00000DEADBEA7DAD; // invalid return address. 250 | 251 | // push stackContents 252 | err = vm_write( remoteTask, remoteStack, 253 | (pointer_t) stackContents, STACK_CONTENTS_SIZE); 254 | 255 | remoteThreadState.__rdi = (unsigned long long) (imageOffset); 256 | remoteThreadState.__rsi = (unsigned long long) (remoteParamBlock); 257 | remoteThreadState.__rdx = (unsigned long long) (paramSize); 258 | remoteThreadState.__rcx = (unsigned long long) (dummy_thread_struct); 259 | 260 | // set remote Program Counter 261 | remoteThreadState.__rip = (unsigned long long) (remoteCode); 262 | remoteThreadState.__rip += threadEntryOffset; 263 | 264 | // set remote Stack Pointer 265 | ASSERT_CAST( unsigned long long, remoteStack ); 266 | remoteThreadState.__rsp = (unsigned long long) remoteStack; 267 | 268 | // create thread and launch it 269 | err = thread_create_running( remoteTask, x86_THREAD_STATE64, 270 | (thread_state_t) &remoteThreadState, x86_THREAD_STATE64_COUNT, 271 | &remoteThread ); 272 | } 273 | #else 274 | #error architecture not supported 275 | #endif 276 | 277 | if( err ) { 278 | MACH_ERROR("mach_inject failing..", err); 279 | if( remoteParamBlock ) 280 | vm_deallocate( remoteTask, remoteParamBlock, paramSize ); 281 | if( remoteCode ) 282 | vm_deallocate( remoteTask, remoteCode, imageSize ); 283 | if( remoteStack ) 284 | vm_deallocate( remoteTask, remoteStack, stackSize ); 285 | } 286 | 287 | return err; 288 | } 289 | 290 | mach_error_t 291 | machImageForPointer( 292 | const void *pointer, 293 | const void **image, 294 | unsigned long *size, 295 | unsigned int *jumpTableOffset, 296 | unsigned int *jumpTableSize ) 297 | { 298 | assert( pointer ); 299 | assert( image ); 300 | assert( size ); 301 | 302 | unsigned long p = (unsigned long) pointer; 303 | 304 | if (jumpTableOffset && jumpTableSize) { 305 | *jumpTableOffset = 0; 306 | *jumpTableSize = 0; 307 | } 308 | 309 | unsigned long imageIndex = 0, imageCount = _dyld_image_count(); 310 | for( imageIndex = 0; imageIndex < imageCount; imageIndex++ ) { 311 | #if defined(__x86_64__) 312 | const struct mach_header_64 *header = (const struct mach_header_64 *)_dyld_get_image_header( (uint32_t)imageIndex ); // why no function that returns mach_header_64 313 | const struct section_64 *section = getsectbynamefromheader_64( header, SEG_TEXT, SECT_TEXT ); 314 | #else 315 | const struct mach_header *header = (const struct mach_header *)_dyld_get_image_header( imageIndex ); 316 | const struct section *section = getsectbynamefromheader( header, SEG_TEXT, SECT_TEXT ); 317 | #endif 318 | if (section == 0) continue; 319 | long start = section->addr + _dyld_get_image_vmaddr_slide( (uint32_t)imageIndex ); 320 | long stop = start + section->size; 321 | //printf("start %ld %p %s b\n", start, header, _dyld_get_image_name(imageIndex)); 322 | if( p >= start && p <= stop ) { 323 | // It is truely insane we have to stat() the file system in order 324 | // to discover the size of an in-memory data structure. 325 | const char *imageName = _dyld_get_image_name( (uint32_t)imageIndex ); 326 | assert( imageName ); 327 | struct stat sb; 328 | if( stat( imageName, &sb ) ) 329 | return unix_err( errno ); 330 | if( image ) { 331 | ASSERT_CAST( void*, header ); 332 | *image = (void*) header; 333 | } 334 | if( size ) { 335 | ;//assertUInt32( st_size ); 336 | *size = sb.st_size; 337 | 338 | // needed for Universal binaries. Check if file is fat and get image size from there. 339 | int fd = open (imageName, O_RDONLY); 340 | size_t mapSize = *size; 341 | char * fileImage = mmap (NULL, mapSize, PROT_READ, MAP_FILE|MAP_SHARED, fd, 0); 342 | 343 | assert(fileImage != MAP_FAILED); 344 | struct fat_header* fatHeader = (struct fat_header *)fileImage; 345 | if (fatHeader->magic == OSSwapBigToHostInt32(FAT_MAGIC)) { 346 | //printf("This is a fat binary\n"); 347 | uint32_t archCount = OSSwapBigToHostInt32(fatHeader->nfat_arch); 348 | 349 | NXArchInfo const *localArchInfo = NXGetLocalArchInfo(); 350 | 351 | struct fat_arch* arch = (struct fat_arch *)(fileImage + sizeof(struct fat_header)); 352 | struct fat_arch* matchingArch = NULL; 353 | 354 | int archIndex = 0; 355 | for (archIndex = 0; archIndex < archCount; archIndex++) { 356 | cpu_type_t cpuType = OSSwapBigToHostInt32(arch[archIndex].cputype); 357 | cpu_subtype_t cpuSubtype = OSSwapBigToHostInt32(arch[archIndex].cpusubtype); 358 | 359 | if (localArchInfo->cputype == cpuType) { 360 | matchingArch = arch + archIndex; 361 | if (localArchInfo->cpusubtype == cpuSubtype) break; 362 | } 363 | } 364 | 365 | if (matchingArch) { 366 | *size = OSSwapBigToHostInt32(matchingArch->size); 367 | //printf ("found arch-specific size : %p\n", *size); 368 | } 369 | } 370 | 371 | munmap (fileImage, mapSize); 372 | close(fd); 373 | } 374 | #if defined(__i386__) // this segment is only available on IA-32 375 | if (jumpTableOffset && jumpTableSize) { 376 | const struct section * jumpTableSection = getsectbynamefromheader( header, SEG_IMPORT, "__jump_table" ); 377 | 378 | if (!jumpTableSection) { 379 | unsigned char *start, *end; 380 | jumpTableSection = getsectbynamefromheader( header, SEG_TEXT, "__symbol_stub" ); 381 | /* 382 | start = end = (char *) header + jumpTableSection->offset; 383 | end += jumpTableSection->size; 384 | 385 | fprintf(stderr, "start: %p\n", start); 386 | for (; start < end; start += 6) { 387 | fprintf(stderr, "%p: %p: %p\n", 388 | start, 389 | *(void **)(start+2), 390 | **(void ***)(start+2)); 391 | } 392 | */ 393 | } 394 | 395 | if (jumpTableSection) { 396 | *jumpTableOffset = jumpTableSection->offset; 397 | *jumpTableSize = jumpTableSection->size; 398 | } 399 | } 400 | #endif 401 | return err_none; 402 | } 403 | } 404 | 405 | return err_threadEntry_image_not_found; 406 | } 407 | 408 | #if defined(__i386__) 409 | void* fixedUpImageFromImage ( 410 | const void *image, 411 | unsigned long imageSize, 412 | unsigned int jumpTableOffset, 413 | unsigned int jumpTableSize, 414 | ptrdiff_t fixUpOffset) 415 | { 416 | // first copy the full image 417 | void *fixedUpImage = (void *) malloc ((size_t)imageSize); 418 | bcopy(image, fixedUpImage, imageSize); 419 | 420 | // address of jump table in copied image 421 | void *jumpTable = fixedUpImage + jumpTableOffset; 422 | 423 | 424 | /* indirect jump table */ 425 | if (*(unsigned char *) jumpTable == 0xff) { 426 | // each indirect JMP instruction is 6 bytes (FF xx xx xx xx xx) where FF is the opcode for JMP 427 | int jumpTableCount = jumpTableSize / 6; 428 | 429 | // skip first "ff xx" 430 | jumpTable += 2; 431 | 432 | int entry=0; 433 | for (entry = 0; entry < jumpTableCount; entry++) { 434 | void *jmpValue = *((void **)jumpTable); 435 | /* 436 | fprintf(stderr, "at %p correcting %p to %p\n", 437 | (char *)jumpTable -2, 438 | jmpValue, jmpValue + fixUpOffset); 439 | */ 440 | jmpValue -= fixUpOffset; 441 | *((void **)jumpTable) = jmpValue; 442 | jumpTable+=6; 443 | } 444 | } 445 | else { 446 | // each JMP instruction is 5 bytes (E9 xx xx xx xx) where E9 is the opcode for JMP 447 | int jumpTableCount = jumpTableSize / 5; 448 | 449 | // skip first "E9" 450 | jumpTable++; 451 | 452 | int entry=0; 453 | for (entry = 0; entry < jumpTableCount; entry++) { 454 | unsigned int jmpValue = *((unsigned int *)jumpTable); 455 | jmpValue += fixUpOffset; 456 | *((unsigned int *)jumpTable) = jmpValue; 457 | jumpTable+=5; 458 | } 459 | } 460 | 461 | return fixedUpImage; 462 | } 463 | #endif /* __i386__ */ 464 | -------------------------------------------------------------------------------- /Helper/mach_inject.h: -------------------------------------------------------------------------------- 1 | // mach_inject.h semver:1.2.0 2 | // Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/mach_inject 5 | 6 | #ifndef _mach_inject_ 7 | #define _mach_inject_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include // for ptrdiff_t 13 | 14 | #define err_threadEntry_image_not_found (err_local|1) 15 | 16 | #define INJECT_ENTRY injectEntry 17 | #define INJECT_ENTRY_SYMBOL "injectEntry" 18 | 19 | typedef void (*mach_inject_entry)( ptrdiff_t codeOffset, void *paramBlock, 20 | size_t paramSize, void* dummy_pthread_data ); 21 | 22 | __BEGIN_DECLS 23 | 24 | /******************************************************************************* 25 | Starts executing threadEntry in a new thread in the process specified by 26 | targetProcess. 27 | 28 | @param threadEntry -> Required pointer to injected thread's entry 29 | point. 30 | @param paramBlock -> Optional pointer to block of memory to pass to 31 | the injected thread. 32 | @param paramSize -> Optional size of paramBlock. 33 | @param targetProcess -> Required target process ID. 34 | @param stackSize -> Optional stack size of threadEntry's thread. Set 35 | to zero for default (currently 8K usable). 36 | @result <- mach_error_t 37 | 38 | ***************************************************************************/ 39 | 40 | mach_error_t 41 | mach_inject( 42 | const mach_inject_entry threadEntry, 43 | const void *paramBlock, 44 | size_t paramSize, 45 | pid_t targetProcess, 46 | vm_size_t stackSize ); 47 | 48 | /******************************************************************************* 49 | Given a pointer, returns its Mach-O image and image size. 50 | 51 | @param pointer -> Required pointer. 52 | @param image <- Optional returned pointer to image (really a 53 | mach_header). 54 | @param size <- Optional returned size of the image. 55 | @param jumpTableOffset <- Optional returned offset of jump table within image (useful on intel) 56 | @param jumpTableSize <- Optional returned size of jump table (useful on intel) 57 | @result <- mach_error_t 58 | 59 | ***************************************************************************/ 60 | 61 | mach_error_t 62 | machImageForPointer( 63 | const void *pointer, 64 | const void **image, 65 | unsigned long *size, 66 | unsigned int *jumpTableOffset, 67 | unsigned int *jumpTableSize ); 68 | 69 | __END_DECLS 70 | #endif // _mach_inject_ -------------------------------------------------------------------------------- /Helper/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Injector 4 | // 5 | // Created by Erwan Barrier on 8/7/12. 6 | // Copyright (c) 2012 Erwan Barrier. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "Helper.h" 12 | 13 | #pragma clang diagnostic push 14 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 15 | 16 | dispatch_source_t g_timer_source = NULL; 17 | 18 | int main(int argc, char *argv[]) 19 | { 20 | // Init idle-exit timer 21 | dispatch_queue_t mq = dispatch_get_main_queue(); 22 | g_timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, mq); 23 | assert(g_timer_source != NULL); 24 | 25 | /* When the idle-exit timer fires, we just call exit(2) with status 0. */ 26 | dispatch_set_context(g_timer_source, NULL); 27 | dispatch_source_set_event_handler_f(g_timer_source, (void (*)(void *))exit); 28 | /* We start off with our timer armed. This is for the simple reason that, 29 | * upon kicking off the GCD state engine, the first thing we'll get to is 30 | * a connection on our socket which will disarm the timer. Remember, handling 31 | * new connections and the firing of the idle-exit timer are synchronized. 32 | */ 33 | dispatch_time_t t0 = dispatch_time(DISPATCH_TIME_NOW, 5llu * NSEC_PER_SEC); 34 | dispatch_source_set_timer(g_timer_source, t0, 0llu, 0llu); 35 | dispatch_resume(g_timer_source); 36 | 37 | 38 | // Check in mach service 39 | launch_data_t req = launch_data_new_string(LAUNCH_KEY_CHECKIN); 40 | assert(req != NULL); 41 | 42 | launch_data_t resp = launch_msg(req); 43 | assert(resp != NULL); 44 | assert(launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY); 45 | 46 | launch_data_t machs = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_MACHSERVICES); 47 | assert(machs != NULL); 48 | assert(launch_data_get_type(machs) == LAUNCH_DATA_DICTIONARY); 49 | 50 | launch_data_t machPortData = launch_data_dict_lookup(machs, HELPER_MACH_ID); 51 | 52 | mach_port_t mp = launch_data_get_machport(machPortData); 53 | launch_data_free(req); 54 | launch_data_free(resp); 55 | 56 | NSMachPort *rp = [[NSMachPort alloc] initWithMachPort:mp]; 57 | NSConnection *c = [NSConnection connectionWithReceivePort:rp sendPort:nil]; 58 | 59 | Helper *injector = [Helper new]; 60 | [c setRootObject:injector]; 61 | 62 | [[NSRunLoop currentRunLoop] run]; 63 | 64 | return (0); 65 | } 66 | -------------------------------------------------------------------------------- /InjectionBundle/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2017 John Holdsworth. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /InjectionBundle/XprobeSwift-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "SimpleSocket.h" 6 | #import "UserDefaults.h" 7 | #import "InjectionClient.h" 8 | #if SWIFT_PACKAGE 9 | #import "../XprobePlugin/Sources/Xprobe/include/Xprobe.h" 10 | #endif 11 | #import "DLKitC.h" 12 | 13 | @interface NSObject(InjectionSweep) 14 | - (void)bsweep; 15 | @end 16 | 17 | @interface NSObject(RunXCTestCase) 18 | + (void)runXCTestCase:(Class)aTestCase; 19 | @end 20 | -------------------------------------------------------------------------------- /InjectionIII.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InjectionIII.xcodeproj/xcshareddata/xcschemes/InjectionIII.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /InjectionIII/App.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/InjectionIII/App.icns -------------------------------------------------------------------------------- /InjectionIII/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /InjectionIII/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Menlo-Regular;\f2\fmodern\fcharset0 Courier; 3 | \f3\fnil\fcharset0 HelveticaNeue;} 4 | {\colortbl;\red255\green255\blue255;\red63\green110\blue116;\red255\green255\blue255;\red83\green98\blue108; 5 | \red0\green0\blue0;\red131\green108\blue40;\red14\green14\blue255;} 6 | {\*\expandedcolortbl;;\csgenericrgb\c24700\c43100\c45600;\csgenericrgb\c100000\c100000\c100000;\csgenericrgb\c32549\c38431\c42353; 7 | \csgenericrgb\c0\c0\c0;\csgenericrgb\c51200\c42300\c15700;\csgenericrgb\c5500\c5500\c100000;} 8 | \paperw11900\paperh16840\vieww9600\viewh8400\viewkind0 9 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\partightenfactor0 10 | 11 | \f0\fs24 \cf0 InjectionIII.app is a mostly Swift rewrite of {\field{\*\fldinst{HYPERLINK "https://github.com/johnno1962/injectionforxcode"}}{\fldrslt 12 | \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:\ 13 | \ 14 | \pard\tx543\pardeftab543\pardirnatural\partightenfactor0 15 | 16 | \f2 \cf2 \cb3 #if DEBUG\cf0 \ 17 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()\cf0 \ 18 | \cf2 //for tvOS:\cf0 \ 19 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()\cf0 \ 20 | \cf2 //Or for macOS:\cf0 \ 21 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()\cf0 \ 22 | \cf2 #endif\cf0 \ 23 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab543\partightenfactor0 24 | 25 | \f0 \cf0 \cb1 \ 26 | Or, to use with Xcode 10:\ 27 | \ 28 | \pard\tx543\pardeftab543\pardirnatural\partightenfactor0 29 | 30 | \f2 \cf2 \cb3 #if DEBUG\cf0 \ 31 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()\cf0 \ 32 | \cf2 //for tvOS:\cf0 \ 33 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle")?.load()\cf0 \ 34 | \cf2 //Or for macOS:\cf0 \ 35 | \cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle")?.load()\cf0 \ 36 | \cf2 #endif\cf0 \ 37 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab543\partightenfactor0 38 | 39 | \f0 \cf0 \cb1 \ 40 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\partightenfactor0 41 | \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}}\ 42 | \ 43 | The fabulous app icon is thanks to Katya of {\field{\*\fldinst{HYPERLINK "http://pixel-mixer.com/"}}{\fldrslt pixel-mixer.com}}.\ 44 | \ 45 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 46 | 47 | \f1\fs22 \cf0 MIT License\ 48 | \ 49 | Copyright (C) 2016-22 John Holdsworth {\field{\*\fldinst{HYPERLINK "mailto:injectionIII@johnholdsworth.com"}}{\fldrslt injectionIII@johnholdsworth.com}} {\field{\*\fldinst{HYPERLINK "https://twitter.com/Injection4Xcode"}}{\fldrslt 50 | \fs24 \cf4 \expnd0\expndtw0\kerning0 51 | @Injection4Xcode}}\ 52 | \ 53 | \pard\pardeftab720\partightenfactor0 54 | 55 | \fs24 \cf0 \expnd0\expndtw0\kerning0 56 | Permission is hereby granted, free of charge, to any person obtaining a copy\ 57 | of this software and associated documentation files (the "Software"), to deal\ 58 | in the Software without restriction, including without limitation the rights\ 59 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ 60 | copies of the Software, and to permit persons to whom the Software is\ 61 | furnished to do so, subject to the following conditions:\ 62 | \ 63 | The above copyright notice and this permission notice shall be included in\ 64 | all copies or substantial portions of the Software.\ 65 | \ 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \ 67 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. \ 68 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \ 69 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \ 70 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ 71 | \pard\tx543\pardeftab543\pardirnatural\partightenfactor0 72 | 73 | \f3\fs26 \cf5 \cb3 \kerning1\expnd0\expndtw0 \ 74 | This release includes a very slightly modified version of the excellent 75 | \f0\fs24 \cf0 \ 76 | 77 | \f3\fs26 \cf6 [\cf5 canviz\cf6 ](\cf7 https://code.google.com/p/canviz/\cf6 )\cf5 library to render "dot" files 78 | \f0\fs24 \cf0 \ 79 | 80 | \f3\fs26 \cf5 in an HTML canvas which is subject to an MIT license. The changes are to pass 81 | \f0\fs24 \cf0 \ 82 | 83 | \f3\fs26 \cf5 through the ID of the node to the node label tag (line 212), to reverse 84 | \f0\fs24 \cf0 \ 85 | 86 | \f3\fs26 \cf5 the rendering of nodes and the lines linking them (line 406) and to 87 | \f0\fs24 \cf0 \ 88 | 89 | \f3\fs26 \cf5 store edge paths so they can be colored (line 66 and 303) in "canviz-0.1/canviz.js". 90 | \f0\fs24 \cf0 \ 91 | \ 92 | 93 | \f3\fs26 \cf5 It now also includes \cf6 [\cf5 CodeMirror\cf6 ](\cf7 http://codemirror.net/\cf6 )\cf5 JavaScript editor 94 | \f0\fs24 \cf0 \ 95 | 96 | \f3\fs26 \cf5 for the code to be evaluated using injection under an MIT license. 97 | \f0\fs24 \cf0 \ 98 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 99 | 100 | \f1\fs22 \cf0 \cb1 \ 101 | \pard\tx543\pardeftab543\pardirnatural\partightenfactor0 102 | 103 | \f3\fs26 \cf5 \cb3 Consult the app repo above for details of other software included under an MIT license.\ 104 | } -------------------------------------------------------------------------------- /InjectionIII/HelperInstaller.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelperInstaller.h 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HelperInstaller : NSObject 12 | 13 | + (BOOL)isInstalled; 14 | + (BOOL)install:(NSError **)error; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /InjectionIII/HelperInstaller.m: -------------------------------------------------------------------------------- 1 | // 2 | // HelperInstaller.m 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "HelperInstaller.h" 10 | #import 11 | 12 | @implementation HelperInstaller 13 | 14 | + (NSString *)kInjectionHelperID { 15 | return [[[[NSBundle mainBundle] infoDictionary] valueForKey:(NSString *)kCFBundleIdentifierKey] stringByAppendingString:@".Helper"]; 16 | } 17 | 18 | + (BOOL)isInstalled { 19 | NSString *helperPath = [@"/Library/PrivilegedHelperTools" stringByAppendingPathComponent:[self kInjectionHelperID]]; 20 | return [[NSFileManager defaultManager] fileExistsAtPath:helperPath]; 21 | } 22 | 23 | + (BOOL)install:(NSError **)error { 24 | AuthorizationRef authRef = NULL; 25 | BOOL result = [self askPermission:&authRef error:error]; 26 | 27 | if (result == YES) { 28 | result = [self installHelperTool:[self kInjectionHelperID] authorizationRef:authRef error:error]; 29 | } 30 | 31 | if (result == YES) { 32 | NSLog(@"Installed v%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); 33 | } 34 | 35 | return result; 36 | } 37 | 38 | + (BOOL)askPermission:(AuthorizationRef *)authRef error:(NSError **)error { 39 | // Creating auth item to bless helper tool and install framework 40 | AuthorizationItem authItem = {kSMRightBlessPrivilegedHelper, 0, NULL, 0}; 41 | 42 | // Creating a set of authorization rights 43 | AuthorizationRights authRights = {1, &authItem}; 44 | 45 | // Specifying authorization options for authorization 46 | AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights; 47 | 48 | // Open dialog and prompt user for password 49 | OSStatus status = AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, authRef); 50 | 51 | if (status == errAuthorizationSuccess) { 52 | return YES; 53 | } else { 54 | *error = [NSError errorWithDomain:[NSBundle mainBundle].bundleIdentifier 55 | code:status 56 | userInfo:@{NSLocalizedDescriptionKey: @"Authorisation error"}]; 57 | return NO; 58 | } 59 | } 60 | 61 | + (BOOL)installHelperTool:(NSString *)executableLabel authorizationRef:(AuthorizationRef)authRef error:(NSError **)error { 62 | CFErrorRef blessError = NULL; 63 | BOOL result = SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)executableLabel, authRef, &blessError); 64 | 65 | if (result == NO) { 66 | NSLog(@"Could not install %@ - %@", executableLabel, blessError); 67 | *error = (__bridge NSError *)blessError; 68 | } else { 69 | NSLog(@"Installed %@ successfully", executableLabel); 70 | } 71 | 72 | return result; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /InjectionIII/HelperProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelperProxy.h 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HelperProxy : NSObject 12 | 13 | + (BOOL)inject:(NSString *)bundlePath error:(NSError **)error; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /InjectionIII/HelperProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // HelperProxy.m 3 | // Smuggler 4 | // 5 | // Created by John Holdsworth on 24/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "HelperProxy.h" 10 | #import "Helper.h" 11 | 12 | static unsigned dlopenPageOffset, dlerrorPageOffset; 13 | 14 | @implementation HelperProxy 15 | 16 | + (BOOL)inject:(NSString *)bundlePath error:(NSError **)error { 17 | NSConnection *c = [NSConnection connectionWithRegisteredName:@HELPER_MACH_ID host:nil]; 18 | assert(c != nil); 19 | 20 | Helper *helper = (Helper *)[c rootProxy]; 21 | assert(helper != nil); 22 | 23 | NSLog(@"Injecting %@", bundlePath); 24 | 25 | mach_error_t err = [helper inject:[NSBundle mainBundle].bundlePath bundle:bundlePath client:__FILE__ 26 | dlopenPageOffset: dlopenPageOffset dlerrorPageOffset: dlerrorPageOffset]; 27 | 28 | if (err == 0) { 29 | NSLog(@"Injected Simulator"); 30 | return YES; 31 | } else { 32 | NSString *description; 33 | switch( err ) { 34 | case SMHelperErrorsPayload: description = @"Unable to init payload"; break; 35 | case SMHelperErrorsNoSim: description = @"Simulator is not running"; break; 36 | case SMHelperErrorsNoNm: description = @"Unable to find dlopen. Is xcode-select correct?"; break; 37 | case SMHelperErrorsNoApp: description = @"Could not find App running in simulator"; break; 38 | case SMHelperErrors32Bits: description = @"Injection only possible for 64 bit targets"; break; 39 | default: 40 | description = [NSString stringWithCString:mach_error_string(err) ?: "Unkown mach error" encoding:NSASCIIStringEncoding]; 41 | } 42 | 43 | NSLog(@"an error occurred while injecting Simulator: %@ (error code: %d)", description, (int)err); 44 | 45 | *error = [NSError errorWithDomain:[NSBundle mainBundle].bundleIdentifier 46 | code:err 47 | userInfo:@{NSLocalizedDescriptionKey: description}]; 48 | return NO; 49 | } 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /InjectionIII/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | App.icns 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 8233 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2017-20 John Holdsworth. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /InjectionIII/InjectionBusy.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/InjectionIII/InjectionBusy.tif -------------------------------------------------------------------------------- /InjectionIII/InjectionError.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/InjectionIII/InjectionError.tif -------------------------------------------------------------------------------- /InjectionIII/InjectionIII-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | #import 7 | 8 | #import "XcodeHash.h" 9 | #import "UserDefaults.h" 10 | #import "SimpleSocket.h" 11 | #import "SignerService.h" 12 | #import "InjectionClient.h" 13 | #if __has_include("../XprobePlugin/Sources/XprobeUI/include/XprobePluginMenuController.h") 14 | #import "RMWindowController.h" 15 | #import "../XprobePlugin/Sources/XprobeUI/include/XprobePluginMenuController.h" 16 | #endif 17 | 18 | #import "DDHotKeyCenter.h" 19 | #import 20 | -------------------------------------------------------------------------------- /InjectionIII/InjectionIII.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.print 12 | 13 | com.apple.security.files.bookmarks.app-scope 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /InjectionIII/InjectionIIISalt.h: -------------------------------------------------------------------------------- 1 | #define INJECTION_SALT 676611800 2 | -------------------------------------------------------------------------------- /InjectionIII/InjectionIdle.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/InjectionIII/InjectionIdle.tif -------------------------------------------------------------------------------- /InjectionIII/InjectionOK.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/InjectionIII/InjectionOK.tif -------------------------------------------------------------------------------- /InjectionIII/UpdateCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateCheck.swift 3 | // InjectionIII 4 | // 5 | // Created by John Holdsworth on 17/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/ResidentEval/InjectionIII/UpdateCheck.swift#8 $ 9 | // 10 | 11 | import Foundation 12 | 13 | extension AppDelegate { 14 | 15 | @IBAction func updateCheck(_ sender: NSMenuItem?) { 16 | 17 | URLSession(configuration: .default).dataTask(with: 18 | URL(string: 19 | "https://api.github.com/repos/johnno1962/InjectionIII/releases")!) { 20 | data, response, error in 21 | do { 22 | if let data = data { 23 | let decoder = JSONDecoder() 24 | decoder.keyDecodingStrategy = .convertFromSnakeCase 25 | decoder.dateDecodingStrategy = .iso8601 26 | let releases = try decoder.decode([Release].self, from: data) 27 | 28 | DispatchQueue.main.async { 29 | guard let latest = releases 30 | .first(where: { !$0.prerelease }), 31 | let available = latest.tagName, 32 | let current = Bundle.main.object( 33 | forInfoDictionaryKey: "CFBundleShortVersionString") 34 | as? String, 35 | available.compare(current, options: .numeric) 36 | == .orderedDescending else { 37 | if sender != nil { 38 | let alert = NSAlert() 39 | alert.addButton(withTitle: "OK") 40 | alert.addButton(withTitle: "Check Monthly") 41 | alert.messageText = "You are running the latest released version." 42 | switch alert.runModal() { 43 | case .alertSecondButtonReturn: 44 | self.updateItem.state = .on 45 | default: 46 | break 47 | } 48 | } 49 | self.setUpdateCheck() 50 | return 51 | } 52 | 53 | let fmt = DateFormatter() 54 | fmt.dateStyle = .medium 55 | let alert = NSAlert() 56 | alert.messageText = "New build \(available) (\(fmt.string(from: latest.publishedAt))) is available." 57 | alert.informativeText = latest.body 58 | alert.addButton(withTitle: "View") 59 | alert.addButton(withTitle: "Download") 60 | alert.addButton(withTitle: "Later") 61 | switch alert.runModal() { 62 | case .alertFirstButtonReturn: 63 | NSWorkspace.shared.open(latest.htmlUrl) 64 | case .alertSecondButtonReturn: 65 | NSWorkspace.shared.open(latest 66 | .assets[0].browserDownloadUrl) 67 | default: 68 | break 69 | } 70 | self.setUpdateCheck() 71 | } 72 | } 73 | else if let error = error { 74 | throw error 75 | } 76 | } catch { 77 | DispatchQueue.main.async { 78 | NSAlert(error: error).runModal() 79 | } 80 | } 81 | }.resume() 82 | } 83 | 84 | func setUpdateCheck() { 85 | if updateItem.state == .on { 86 | defaults.set(Date.timeIntervalSinceReferenceDate + 87 | 30 * 24 * 60 * 60, forKey: UserDefaultsUpdateCheck) 88 | } 89 | } 90 | 91 | struct Release: Decodable { 92 | let htmlUrl: URL 93 | let tagName: String? 94 | let prerelease: Bool 95 | let publishedAt: Date 96 | let assets: [Asset] 97 | let body: String 98 | } 99 | 100 | struct Asset: Decodable { 101 | let browserDownloadUrl: URL 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /InjectionIII/Xcode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Xcode.h -- extracted using: sdef /Applications/Xcode.app | sdp -fh --basename Xcode 3 | */ 4 | 5 | #import 6 | #import 7 | 8 | 9 | @class XcodeApplication, XcodeDocument, XcodeWindow, XcodeFileDocument, XcodeTextDocument, XcodeSourceDocument, XcodeWorkspaceDocument, XcodeSchemeActionResult, XcodeSchemeActionIssue, XcodeBuildError, XcodeBuildWarning, XcodeAnalyzerIssue, XcodeTestFailure, XcodeScheme, XcodeRunDestination, XcodeDevice, XcodeBuildConfiguration, XcodeProject, XcodeBuildSetting, XcodeResolvedBuildSetting, XcodeTarget; 10 | 11 | // Category added for use with Swift 12 | @interface SBObject(InjectionIII) 13 | - (id) open:(id)x; // Open a document. 14 | @property (copy) NSString *path; // The document's path. 15 | @property (copy) NSArray *selectedCharacterRange; // The first and last character positions in the selection. 16 | @property (copy) XcodeWorkspaceDocument *activeWorkspaceDocument; // The active workspace document in Xcode. 17 | @end 18 | 19 | enum XcodeSaveOptions { 20 | XcodeSaveOptionsYes = 'yes ' /* Save the file. */, 21 | XcodeSaveOptionsNo = 'no ' /* Do not save the file. */, 22 | XcodeSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */ 23 | }; 24 | typedef enum XcodeSaveOptions XcodeSaveOptions; 25 | 26 | // The status of a scheme action result object. 27 | enum XcodeSchemeActionResultStatus { 28 | XcodeSchemeActionResultStatusNotYetStarted = 'srsn' /* The action has not yet started. */, 29 | XcodeSchemeActionResultStatusRunning = 'srsr' /* The action is in progress. */, 30 | XcodeSchemeActionResultStatusCancelled = 'srsc' /* The action was cancelled. */, 31 | XcodeSchemeActionResultStatusFailed = 'srsf' /* The action ran but did not complete successfully. */, 32 | XcodeSchemeActionResultStatusErrorOccurred = 'srse' /* The action was not able to run due to an error. */, 33 | XcodeSchemeActionResultStatusSucceeded = 'srss' /* The action succeeded. */ 34 | }; 35 | typedef enum XcodeSchemeActionResultStatus XcodeSchemeActionResultStatus; 36 | 37 | @protocol XcodeGenericMethods 38 | 39 | - (void) closeSaving:(XcodeSaveOptions)saving savingIn:(NSURL *)savingIn; // Close a document. 40 | - (void) delete; // Delete an object. 41 | - (void) moveTo:(SBObject *)to; // Move an object to a new location. 42 | - (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. 43 | - (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. 44 | - (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. 45 | - (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. 46 | - (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. 47 | 48 | @end 49 | 50 | 51 | 52 | /* 53 | * Standard Suite 54 | */ 55 | 56 | // The application's top-level scripting object. 57 | @interface XcodeApplication : SBApplication 58 | 59 | - (SBElementArray *) documents; 60 | - (SBElementArray *) windows; 61 | 62 | @property (copy, readonly) NSString *name; // The name of the application. 63 | @property (readonly) BOOL frontmost; // Is this the active application? 64 | @property (copy, readonly) NSString *version; // The version number of the application. 65 | 66 | - (id) open:(id)x; // Open a document. 67 | - (void) quitSaving:(XcodeSaveOptions)saving; // Quit the application. 68 | - (BOOL) exists:(id)x; // Verify that an object exists. 69 | 70 | @end 71 | 72 | // A document. 73 | @interface XcodeDocument : SBObject 74 | 75 | @property (copy, readonly) NSString *name; // Its name. 76 | @property (readonly) BOOL modified; // Has it been modified since the last save? 77 | @property (copy, readonly) NSURL *file; // Its location on disk, if it has one. 78 | 79 | 80 | @end 81 | 82 | // A window. 83 | @interface XcodeWindow : SBObject 84 | 85 | @property (copy, readonly) NSString *name; // The title of the window. 86 | - (NSInteger) id; // The unique identifier of the window. 87 | @property NSInteger index; // The index of the window, ordered front to back. 88 | @property NSRect bounds; // The bounding rectangle of the window. 89 | @property (readonly) BOOL closeable; // Does the window have a close button? 90 | @property (readonly) BOOL miniaturizable; // Does the window have a minimize button? 91 | @property BOOL miniaturized; // Is the window minimized right now? 92 | @property (readonly) BOOL resizable; // Can the window be resized? 93 | @property BOOL visible; // Is the window visible right now? 94 | @property (readonly) BOOL zoomable; // Does the window have a zoom button? 95 | @property BOOL zoomed; // Is the window zoomed right now? 96 | @property (copy, readonly) XcodeDocument *document; // The document whose contents are displayed in the window. 97 | 98 | 99 | @end 100 | 101 | 102 | 103 | /* 104 | * Xcode Application Suite 105 | */ 106 | 107 | // The Xcode application. 108 | @interface XcodeApplication (XcodeApplicationSuite) 109 | 110 | - (SBElementArray *) fileDocuments; 111 | - (SBElementArray *) sourceDocuments; 112 | - (SBElementArray *) workspaceDocuments; 113 | 114 | @property (copy) XcodeWorkspaceDocument *activeWorkspaceDocument; // The active workspace document in Xcode. 115 | 116 | @end 117 | 118 | 119 | 120 | /* 121 | * Xcode Document Suite 122 | */ 123 | 124 | // An Xcode-compatible document. 125 | @interface XcodeDocument (XcodeDocumentSuite) 126 | 127 | @property (copy) NSString *path; // The document's path. 128 | 129 | @end 130 | 131 | // A document that represents a file on disk. It also provides access to the window it appears in. 132 | @interface XcodeFileDocument : XcodeDocument 133 | 134 | 135 | @end 136 | 137 | // A document that represents a text file on disk. It also provides access to the window it appears in. 138 | @interface XcodeTextDocument : XcodeFileDocument 139 | 140 | @property (copy) NSArray *selectedCharacterRange; // The first and last character positions in the selection. 141 | @property (copy) NSArray *selectedParagraphRange; // The first and last paragraph positions that contain the selection. 142 | @property (copy) NSString *text; // The text of the text file referenced. 143 | @property BOOL notifiesWhenClosing; // Should Xcode notify other apps when this document is closed? 144 | 145 | 146 | @end 147 | 148 | // A document that represents a source file on disk. It also provides access to the window it appears in. 149 | @interface XcodeSourceDocument : XcodeTextDocument 150 | 151 | 152 | @end 153 | 154 | // A document that represents a workspace on disk. Workspaces are the top-level container for almost all objects and commands in Xcode. 155 | @interface XcodeWorkspaceDocument : XcodeDocument 156 | 157 | - (SBElementArray *) projects; 158 | - (SBElementArray *) schemes; 159 | - (SBElementArray *) runDestinations; 160 | 161 | @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. 162 | @property (copy) XcodeScheme *activeScheme; // The workspace's scheme that will be used for scheme actions. 163 | @property (copy) XcodeRunDestination *activeRunDestination; // The workspace's run destination that will be used for scheme actions. 164 | @property (copy) XcodeSchemeActionResult *lastSchemeActionResult; // The scheme action result for the last scheme action command issued to the workspace document. 165 | @property (copy, readonly) NSURL *file; // The workspace document's location on disk, if it has one. 166 | 167 | 168 | @end 169 | 170 | 171 | 172 | /* 173 | * Xcode Scheme Suite 174 | */ 175 | 176 | // An object describing the result of performing a scheme action command. 177 | @interface XcodeSchemeActionResult : SBObject 178 | 179 | - (SBElementArray *) buildErrors; 180 | - (SBElementArray *) buildWarnings; 181 | - (SBElementArray *) analyzerIssues; 182 | - (SBElementArray *) testFailures; 183 | 184 | - (NSString *) id; // The unique identifier for the scheme. 185 | @property (readonly) BOOL completed; // Whether this scheme action has completed (sucessfully or otherwise) or not. 186 | @property XcodeSchemeActionResultStatus status; // Indicates the status of the scheme action. 187 | @property (copy) NSString *errorMessage; // If the result's status is "error occurred", this will be the error message; otherwise, this will be "missing value". 188 | @property (copy) NSString *buildLog; // If this scheme action performed a build, this will be the text of the build log. 189 | 190 | 191 | @end 192 | 193 | // An issue (like an error or warning) generated by a scheme action. 194 | @interface XcodeSchemeActionIssue : SBObject 195 | 196 | @property (copy) NSString *message; // The text of the issue. 197 | @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. 198 | @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. 199 | @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. 200 | @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. 201 | @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. 202 | 203 | 204 | @end 205 | 206 | // An error generated by a build. 207 | @interface XcodeBuildError : XcodeSchemeActionIssue 208 | 209 | 210 | @end 211 | 212 | // A warning generated by a build. 213 | @interface XcodeBuildWarning : XcodeSchemeActionIssue 214 | 215 | 216 | @end 217 | 218 | // A warning generated by the static analyzer. 219 | @interface XcodeAnalyzerIssue : XcodeSchemeActionIssue 220 | 221 | 222 | @end 223 | 224 | // A failure from a test. 225 | @interface XcodeTestFailure : XcodeSchemeActionIssue 226 | 227 | 228 | @end 229 | 230 | // A set of parameters for building, testing, launching or distributing the products of a workspace. 231 | @interface XcodeScheme : SBObject 232 | 233 | @property (copy, readonly) NSString *name; // The name of the scheme. 234 | - (NSString *) id; // The unique identifier for the scheme. 235 | 236 | 237 | @end 238 | 239 | // An object which specifies parameters such as the device and architecture for which to perform a scheme action. 240 | @interface XcodeRunDestination : SBObject 241 | 242 | @property (copy, readonly) NSString *name; // The name of the run destination, as displayed in Xcode's interface. 243 | @property (copy, readonly) NSString *architecture; // The architecture for which this run destination results in execution. 244 | @property (copy, readonly) NSString *platform; // The identifier of the platform which this run destination targets, such as "macosx", "iphoneos", "iphonesimulator", etc . 245 | @property (copy, readonly) XcodeDevice *device; // The physical or virtual device which this run destination targets. 246 | @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. 247 | 248 | 249 | @end 250 | 251 | // A device which can be used as the target for a scheme action, as part of a run destination. 252 | @interface XcodeDevice : SBObject 253 | 254 | @property (copy, readonly) NSString *name; // The name of the device. 255 | @property (copy, readonly) NSString *deviceIdentifier; // A stable identifier for the device, as shown in Xcode's "Devices" window. 256 | @property (copy, readonly) NSString *operatingSystemVersion; // The version of the operating system installed on the device which this run destination targets. 257 | @property (copy, readonly) NSString *deviceModel; // The model of device (e.g. "iPad Air") which this run destination targets. 258 | @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. 259 | 260 | 261 | @end 262 | 263 | 264 | 265 | /* 266 | * Xcode Project Suite 267 | */ 268 | 269 | // A set of build settings for a target or project. Each target in a project has the same named build configurations as the project. 270 | @interface XcodeBuildConfiguration : SBObject 271 | 272 | - (SBElementArray *) buildSettings; 273 | - (SBElementArray *) resolvedBuildSettings; 274 | 275 | - (NSString *) id; // The unique identifier for the build configuration. 276 | @property (copy, readonly) NSString *name; // The name of the build configuration. 277 | 278 | 279 | @end 280 | 281 | // An Xcode project. Projects represent project files on disk and are always open in the context of a workspace document. 282 | @interface XcodeProject : SBObject 283 | 284 | - (SBElementArray *) buildConfigurations; 285 | - (SBElementArray *) targets; 286 | 287 | @property (copy, readonly) NSString *name; // The name of the project 288 | - (NSString *) id; // The unique identifier for the project. 289 | 290 | 291 | @end 292 | 293 | // A setting that controls how products are built. 294 | @interface XcodeBuildSetting : SBObject 295 | 296 | @property (copy) NSString *name; // The unlocalized build setting name (e.g. DSTROOT). 297 | @property (copy) NSString *value; // A string value for the build setting. 298 | 299 | 300 | @end 301 | 302 | // An object that represents a resolved value for a build setting. 303 | @interface XcodeResolvedBuildSetting : SBObject 304 | 305 | @property (copy) NSString *name; // The unlocalized build setting name (e.g. DSTROOT). 306 | @property (copy) NSString *value; // A string value for the build setting. 307 | 308 | 309 | @end 310 | 311 | // A target is a blueprint for building a product. Targets inherit build settings from their project if not overridden in the target. 312 | @interface XcodeTarget : SBObject 313 | 314 | - (SBElementArray *) buildConfigurations; 315 | 316 | @property (copy) NSString *name; // The name of this target. 317 | - (NSString *) id; // The unique identifier for the target. 318 | @property (copy, readonly) XcodeProject *project; // The project that contains this target 319 | 320 | 321 | @end 322 | 323 | -------------------------------------------------------------------------------- /InjectionIII/XcodeHash.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeHash.h 3 | // Refactorator 4 | // 5 | // Created by John Holdsworth on 19/11/2016. 6 | // 7 | 8 | #import 9 | 10 | @interface XcodeHash : NSObject 11 | + (NSString *)hashStringForPath:(NSString *)path; 12 | @end 13 | -------------------------------------------------------------------------------- /InjectionIII/XcodeHash.m: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeHash.m 3 | // Refactorator 4 | // 5 | // Created by John Holdsworth on 19/11/2016. 6 | // 7 | 8 | #import "XcodeHash.h" 9 | 10 | #import 11 | 12 | @implementation XcodeHash 13 | 14 | // Thanks to: http://samdmarshall.com/blog/xcode_deriveddata_hashes.html 15 | 16 | // this function is used to swap byte ordering of a 64bit integer 17 | static uint64_t swap_uint64(uint64_t val) { 18 | val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); 19 | val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); 20 | return (val << 32) | (val >> 32); 21 | } 22 | 23 | /*! 24 | @method hashStringForPath 25 | 26 | Create the unique identifier string for a Xcode project path 27 | 28 | @param path (input) string path to the ".xcodeproj" or ".xcworkspace" file 29 | 30 | @result NSString* of the identifier 31 | */ 32 | + (NSString *)hashStringForPath:(NSString *)path; 33 | { 34 | // using uint64_t[2] for ease of use, since it is the same size as char[CC_MD5_DIGEST_LENGTH] 35 | uint64_t digest[CC_MD2_DIGEST_LENGTH] = {0}; 36 | 37 | // char array that will contain the identifier 38 | unsigned char resultStr[28] = {0}; 39 | 40 | // setup md5 context 41 | CC_MD5_CTX md5; 42 | CC_MD5_Init(&md5); 43 | 44 | // get the UTF8 string of the path 45 | const char *c_path = [path UTF8String]; 46 | 47 | // get length of the path string 48 | unsigned long length = strlen(c_path); 49 | 50 | // update the md5 context with the full path string 51 | CC_MD5_Update (&md5, c_path, (CC_LONG)length); 52 | 53 | // finalize working with the md5 context and store into the digest 54 | CC_MD5_Final ( (unsigned char *)digest, &md5); 55 | 56 | // take the first 8 bytes of the digest and swap byte order 57 | uint64_t startValue = swap_uint64(digest[0]); 58 | 59 | // for indexes 13->0 60 | int index = 13; 61 | do { 62 | // take 'startValue' mod 26 (restrict to alphabetic) and add based 'a' 63 | resultStr[index] = (char)((startValue % 26) + 'a'); 64 | 65 | // divide 'startValue' by 26 66 | startValue /= 26; 67 | 68 | index--; 69 | } while (index >= 0); 70 | 71 | // The second loop, this time using the last 8 bytes 72 | // repeating the same process as before but over indexes 27->14 73 | startValue = swap_uint64(digest[1]); 74 | index = 27; 75 | do { 76 | resultStr[index] = (char)((startValue % 26) + 'a'); 77 | startValue /= 26; 78 | index--; 79 | } while (index > 13); 80 | 81 | //return [[NSString alloc] initWithString:@"agyjsqofsyrdwafqcrbciitvnmmj"]; 82 | // create a new string from the 'resultStr' char array and return 83 | return [[NSString alloc] initWithBytes:resultStr length:28 encoding:NSUTF8StringEncoding]; 84 | } 85 | 86 | @end 87 | 88 | -------------------------------------------------------------------------------- /InjectionIII/build_bundles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # 3 | # build_bundles.sh 4 | # InjectionIII 5 | # 6 | # Created by John Holdsworth on 04/10/2019. 7 | # Copyright © 2019 John Holdsworth. All rights reserved. 8 | # 9 | # $Id: //depot/ResidentEval/InjectionIII/build_bundles.sh#92 $ 10 | # 11 | 12 | # Injection has to assume a fixed path for Xcode.app as it uses 13 | # Swift and the user's project may contain only Objective-C. 14 | # The second "rpath" is to be able to find XCTest.framework. 15 | FIXED_XCODE_DEVELOPER_PATH=/Applications/Xcode.app/Contents/Developer 16 | export SWIFT_ACTIVE_COMPILATION_CONDITIONS="" 17 | 18 | function build_bundle () { 19 | FAMILY=$1 20 | PLATFORM=$2 21 | SDK=$3 22 | SWIFT_DYLIBS_PATH="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$SDK" 23 | CONCURRENCY_DYLIBS="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/$SDK" 24 | XCTEST_FRAMEWORK_PATH="$FIXED_XCODE_DEVELOPER_PATH/Platforms/$PLATFORM.platform/Developer/Library/Frameworks" 25 | STRACE_VERSIONS="$CODESIGNING_FOLDER_PATH/Contents/Resources/${FAMILY}Injection.bundle/Frameworks/SwiftTrace.framework/Versions" 26 | BUNDLE_CONFIG=Release 27 | 28 | if [ ! -d "$SWIFT_DYLIBS_PATH" -o ! -d "${XCTEST_FRAMEWORK_PATH}/XCTest.framework" ]; then 29 | echo "Missing RPATH $SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH" 30 | exit 1 31 | fi 32 | "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" -sdk $SDK -config $BUNDLE_CONFIG -target SwiftTrace && 33 | "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" $APP_SANDBOXED PRODUCT_NAME="${FAMILY}Injection" LD_RUNPATH_SEARCH_PATHS="$SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH $CONCURRENCY_DYLIBS @loader_path/Frameworks" -sdk $SDK -config $BUNDLE_CONFIG -target InjectionBundle && 34 | "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" PRODUCT_NAME="${FAMILY}SwiftUISupport" -sdk $SDK -config $BUNDLE_CONFIG -target SwiftUISupport && 35 | 36 | rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/*.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" && 37 | mkdir -p "$STRACE_VERSIONS/A/Resources" && 38 | rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/SwiftTrace.framework/{Headers,Modules,SwiftTrace} "$STRACE_VERSIONS/A" && 39 | rm -f "$STRACE_VERSIONS/Current" && ln -s A "$STRACE_VERSIONS/Current" 40 | for thing in SwiftTrace Modules Resources Headers; do 41 | ln -sf Versions/Current/$thing "$CODESIGNING_FOLDER_PATH/Contents/Resources/${FAMILY}Injection.bundle/Frameworks/SwiftTrace.framework" 42 | done 43 | } 44 | 45 | #build_bundle macOS MacOSX macosx && 46 | if [ "$(hostname)" != "Johns-MacBook-Air.local" ]; then 47 | build_bundle xrOS XRSimulator xrsimulator 48 | build_bundle watchOS WatchSimulator watchsimulator 49 | build_bundle tvdevOS AppleTVOS appletvos 50 | build_bundle xrdevOS XROS xros 51 | fi 52 | build_bundle iOS iPhoneSimulator iphonesimulator && 53 | build_bundle tvOS AppleTVSimulator appletvsimulator && 54 | 55 | # iphoneos on M1 mac (requires Sandbox switched off) 56 | build_bundle maciOS iPhoneOS iphoneos && 57 | 58 | # macOSSwiftUISupport needs to be built separately from the main app 59 | "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" -sdk macosx -config $CONFIGURATION -target SwiftUISupport && 60 | 61 | rsync -au $SYMROOT/$CONFIGURATION/macOSSwiftUISupport.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" && 62 | 63 | # Copy across bundles and .swiftinterface files 64 | rsync -au $SYMROOT/$CONFIGURATION/SwiftTrace.framework/Versions/A/{Headers,Modules} "$CODESIGNING_FOLDER_PATH/Contents/Resources/macOSInjection.bundle/Contents/Frameworks/SwiftTrace.framework/Versions/A" && 65 | 66 | for thing in Modules Resources Headers; do 67 | ln -sf Versions/Current/$thing $CODESIGNING_FOLDER_PATH/Contents/Resources/macOSInjection.bundle/Contents/Frameworks/SwiftTrace.framework 68 | done && 69 | 70 | # This seems to be a bug producing .swiftinterface files. 71 | perl -pi.bak -e 's/SwiftTrace.(Swift(Trace|Meta)|dyld_interpose_tuple|rebinding)/$1/g' $CODESIGNING_FOLDER_PATH/Contents/Resources/{macOSInjection.bundle/Contents,{i,maci,tv,xr}OSInjection.bundle}/Frameworks/SwiftTrace.framework/Modules/*/*.swiftinterface && 72 | perl -pi.bak -e 's@(import _Concurrency)@// $1@g' $CODESIGNING_FOLDER_PATH/Contents/Resources/{macOSInjection.bundle/Contents,{i,maci,tv,xr}OSInjection.bundle}/Frameworks/SwiftTrace.framework/Modules/*/*.swiftinterface && 73 | find $CODESIGNING_FOLDER_PATH/Contents/Resources/*.bundle -name '*.bak' -delete 74 | -------------------------------------------------------------------------------- /InjectionIII/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // InjectionIII 4 | // 5 | // Created by John Holdsworth on 06/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /InjectionIII/swift-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # swift-frontend.sh 4 | # InjectionNext 5 | # 6 | # Created by John Holdsworth on 23/02/2025. 7 | # Copyright © 2025 John Holdsworth. All rights reserved. 8 | 9 | FRONTEND="$0" 10 | "$FRONTEND.save" "$@" && 11 | if [ "-c" == "$2" ]; then "/Applications/InjectionIII.app/Contents/Resources/feedcommands" \ 12 | "1.0" "$PWD" "$FRONTEND.save" "$@" >>/tmp/feedcommands.log 2>&1 & fi 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 John Holdsworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InjectionIII.app Project 2 | 3 | ## Yes, HotReloading for Swift 4 | 5 | Chinese language README: [中文集成指南](https://github.com/johnno1962/InjectionIII/blob/main/README_Chinese.md) 6 | 7 | ![Icon](http://johnholdsworth.com/Syringe_128.png) 8 | 9 | Code injection allows you to update the implementation of functions and any method of a class, struct or enum incrementally in the iOS simulator 10 | without having to perform a full rebuild or restart your application. This saves the developer a significant amount of time tweaking code or iterating over a design. Effectively it changes Xcode from being a 11 | "source editor" to being a _"program editor"_ where source changes are 12 | not just saved to disk but into your running program directly. 13 | 14 | ### Stop Press: Injection and Xcode 16.3 15 | 16 | InjectionIII works by recompiling edited source files into a dynamic library 17 | which is then loaded into your app. It determines how to recompile the file 18 | by searching the most recent Xcode build logs for the `swift-frontend` 19 | compiler invocation. Unfortunately, after this having worked for 10 20 | years Xcode 16.3 no longer logs this information by default though it 21 | will if you use "Editor/Add Build Setting/Add User-Defined Setting" 22 | to add a value for `EMIT_FRONTEND_COMMAND_LINES` (set to "YES") to your project's 23 | `Debug` build settings, then InjectionIII can continue to work as before. 24 | 25 | ### How to use it 26 | 27 | Setting up your projects to use injection is now as simple as downloading 28 | one of the [github 29 | releases](https://github.com/johnno1962/InjectionIII/releases) of the app 30 | or from the [Mac App Store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12) 31 | and adding the code below somewhere in your app to be executed on 32 | startup (it is no longer necessary to actually run the app itself). 33 | 34 | ```Swift 35 | #if DEBUG 36 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() 37 | //for tvOS: 38 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load() 39 | //Or for macOS: 40 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load() 41 | #endif 42 | ``` 43 | It's also important to add the options `-Xlinker` and `-interposable` (without double 44 | quotes and on separate lines) to the "Other Linker Flags" of targets in your project 45 | (for the `Debug` configuration only) to enable "interposing" (see the explanation below). 46 | 47 | ![Icon](interposable.png) 48 | 49 | After that, when you run your app in the simulator you should see a 50 | message saying a file watcher has started for your home directory 51 | and, whenever you save a source file in the current project it should 52 | report it has been injected. This means all places that formerly 53 | called the old implementation will have been updated to call the 54 | latest version of your code. 55 | 56 | It's not quite as simple as that as to see results on the screen 57 | immediately the new code needs to have actually been called. 58 | For example, if you inject a view controller it needs to force a 59 | redisplay. To resolve this problem, classes can implement an 60 | `@objc func injected()` method which will be called after the 61 | class has been injected to perform any update to the display. 62 | One technique you can use is to include the following code 63 | somewhere in your program: 64 | 65 | ```Swift 66 | #if DEBUG 67 | extension UIViewController { 68 | @objc func injected() { 69 | viewDidLoad() 70 | } 71 | } 72 | #endif 73 | ``` 74 | Another solution to this problem is "hosting" using the 75 | [Inject](https://github.com/krzysztofzablocki/Inject) 76 | Swift Package introduced by this 77 | [blog post](https://merowing.info/2022/04/hot-reloading-in-swift/). 78 | 79 | ### What injection can't do 80 | 81 | You can't inject changes to how data is laid out in memory i.e. 82 | you cannot add, remove or reorder properties with storage. 83 | For non-final classes this also applies to adding 84 | or removing methods as the `vtable` used for dispatch is 85 | itself a data structure which must not change over injection. 86 | Injection also can't work out what pieces of code need to 87 | be re-executed to update the display as discussed above. 88 | Also, don't get carried away with access control. `private` 89 | properties and methods can't be injected directly, particularly 90 | in extensions as they are not a `global` interposable symbol. 91 | They generally inject indirectly as they can only be accessed 92 | inside the file being injected but this can cause confusion. 93 | Finally, Injection doesn't cope well with source files being 94 | added/renamed/deleted during injection. You may need to 95 | build and relaunch your app or even close and reopen 96 | your project to clear out old Xcode build logs. 97 | 98 | ### Injection of SwiftUI 99 | 100 | SwiftUI is, if anything, better suited to injection than UIKit 101 | as it has specific mechanisms to update the display but you need 102 | to make a couple changes to each `View` struct you want to inject. 103 | To force redraw the simplest way is to add a property that 104 | observes when an injection has occurred: 105 | 106 | ```swift 107 | @ObserveInjection var forceRedraw 108 | ``` 109 | This property wrapper is available in either the 110 | [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) or 111 | [Inject](https://github.com/krzysztofzablocki/Inject) 112 | Swift Package. It essentially contains an `@Published` 113 | integer your views observe that increments with each 114 | injection. You can use one of the following to make one 115 | of these packages available throughout your project: 116 | 117 | ```swift 118 | @_exported import HotSwiftUI 119 | or 120 | @_exported import Inject 121 | ``` 122 | The second change you need to make for reliable SwiftUI 123 | injection is to "erase the return type" of the body property 124 | by wrapping it in `AnyView` using the `.enableInjection()` 125 | method extending `View` in these packages. This is because, 126 | as you add or remove SwiftUI elements it can change the concrete 127 | return type of the body property which amounts to a memory layout 128 | change that may crash. In summary, the tail end of each body should 129 | always look like this: 130 | 131 | ```swift 132 | var body: some View { 133 | VStack or whatever { 134 | // Your SwiftUI code... 135 | } 136 | .enableInjection() 137 | } 138 | 139 | @ObserveInjection var redraw 140 | ``` 141 | You can leave these modifications in your production code as, 142 | for a `Release` build they optimise out to a no-op. 143 | 144 | #### Xcode 16 145 | 146 | New in Xcode 16 is `SWIFT_ENABLE_OPAQUE_TYPE_ERASURE` build setting. 147 | This setting is turned ON by default and you don't need to erase view 148 | body explicitly. You'll still need to `@ObserveInjection` to force redraws. 149 | 150 | For more info, see [Xcode 16.2 release notes](https://developer.apple.com/documentation/xcode-release-notes/xcode-16_2-release-notes). 151 | 152 | ### Injection on an iOS, tvOS or visionOS device 153 | 154 | This can work but you will need to actually run one of the [github 155 | 4.8.0+ releases](https://github.com/johnno1962/InjectionIII/releases) 156 | of the InjectionIII.app, set a user default to opt-in and restart the app. 157 | 158 | ``` 159 | $ defaults write com.johnholdsworth.InjectionIII deviceUnlock any 160 | ``` 161 | Then, instead of loading the injection bundles run this script in a "Build Phase": 162 | (You will also need to turn off the project build setting "User Script Sandboxing") 163 | 164 | ``` 165 | RESOURCES=/Applications/InjectionIII.app/Contents/Resources 166 | if [ -f "$RESOURCES/copy_bundle.sh" ]; then 167 | "$RESOURCES/copy_bundle.sh" 168 | fi 169 | ``` 170 | and, in your application execute the following code on startup: 171 | 172 | ```swift 173 | #if DEBUG 174 | if let path = Bundle.main.path(forResource: 175 | "iOSInjection", ofType: "bundle") ?? 176 | Bundle.main.path(forResource: 177 | "macOSInjection", ofType: "bundle") { 178 | Bundle(path: path)!.load() 179 | } 180 | #endif 181 | ``` 182 | Once you have switched to this configuration it will also 183 | work when using the simulator. Consult the README of the 184 | [HotReloading project](https://github.com/johnno1962/HotReloading) 185 | for details on how to debug having your program connect to the 186 | InjectionIII.app over Wi-Fi. You will also need to select the project 187 | directory for the file watcher manually from the pop-down menu. 188 | 189 | ### Injection on macOS 190 | 191 | It works but you need to temporarily turn off the "app sandbox" and 192 | "library validation" under the "hardened runtime" during development 193 | so it can dynamically load code. In order to avoid codesigning problems, 194 | use the new `copy_bundle.sh` script as detailed in the instructions for 195 | injection on real devices above. 196 | 197 | ### How it works 198 | 199 | Injection has worked various ways over the years, starting out using 200 | the "Swizzling" apis for Objective-C but is now largely built around 201 | a feature of Apple's linker called "interposing" which provides a 202 | solution for any Swift method or computed property of any type. 203 | 204 | When your code calls a function in Swift, it is generally "statically 205 | dispatched", i.e. linked using the "mangled symbol" of the function being called. 206 | Whenever you link your application with the "-interposable" option 207 | however, an additional level of indirection is added where it finds 208 | the address of all functions being called through a section of 209 | writable memory. Using the operating system's ability to load 210 | executable code and the [fishhook](https://github.com/facebook/fishhook) 211 | library to "rebind" the call it is therefore possible to "interpose" 212 | new implementations of any function and effectively stitch 213 | them into the rest of your program at runtime. From that point it will 214 | perform as if the new code had been built into the program. 215 | 216 | Injection uses the `FSEventSteam` api to watch for when a source 217 | file has been changed and scans the last Xcode build log for how to 218 | recompile it and links a dynamic library that can be loaded into your 219 | program. Runtime support for injection then loads the dynamic library 220 | and scans it for the function definitions it contains which it then 221 | "interposes" into the rest of the program. This isn't the full story as 222 | the dispatch of non-final class methods uses a "vtable" (think C++ 223 | virtual methods) which also has to be updated but the project looks 224 | after that along with any legacy Objective-C "swizzling". 225 | 226 | If you are interested knowing more about how injection works 227 | the best source is either my book [Swift Secrets](http://books.apple.com/us/book/id1551005489) or the new, start-over reference implementation 228 | in the [InjectionLite](https://github.com/johnno1962/InjectionLite) 229 | Swift Package. For more information about "interposing" consult [this 230 | blog post](https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html) 231 | or the README of the [fishhook project](https://github.com/facebook/fishhook). 232 | For more information about the organisation of the app itself, consult [ROADMAP.md](https://github.com/johnno1962/InjectionIII/blob/main/ROADMAP.md). 233 | 234 | ### A bit of terminology 235 | 236 | Getting injection to work has three components. A FileWatcher, the code to 237 | recompile any changed files and build a dynamic library that can be loaded 238 | and the injection code itself which stitches the new versions of your code 239 | into the app while it's running. How these three components are combined 240 | gives rise to the number of ways injection can be used. 241 | 242 | "Injection classic" is where you download one of the [binary releases](https://github.com/johnno1962/InjectionIII/releases) 243 | from github and run the InjectionIII.app. You then load one of the bundles 244 | inside that app into your program as shown above in the simulator. 245 | In this configuration, the file watcher and source recompiling is done 246 | inside the app and the bundle connects to the app using a socket to 247 | know when a new dynamic library is ready to be loaded. 248 | 249 | "App Store injection" This version of the app is sandboxed and while 250 | the file watcher still runs inside the app, the recompiling and loading 251 | is delegated to be performed inside the simulator. This can create 252 | problems with C header files as the simulator uses a case sensitive 253 | file system to be a faithful simulation of a real device. 254 | 255 | "HotReloading injection" was where you are running your app on a device 256 | and because you cannot load a bundle off your Mac's filesystem on a real 257 | phone you add the [HotReloading Swift Package](https://github.com/johnno1962/HotReloading) 258 | to your project (during development only!) which contains all the code that 259 | would normally be in the bundle to perform the dynamic loading. This 260 | requires that you use one of the un-sandboxed binary releases. It has 261 | also been replaced by the `copy_bundle.sh` script described above. 262 | 263 | "Standalone injection". This was the most recent evolution of the project 264 | where you don't run the app itself anymore but simply load one of the 265 | injection bundles and the file watcher, re-compilation and injection are 266 | all performed inside the simulator. By default this watches for changes 267 | to any Swift file inside your home directory though you can change this 268 | using the environment variable `INJECTION_DIRECTORIES`. 269 | 270 | [InjectionLite](https://github.com/johnno1962/InjectionLite) is a start-over 271 | minimal implementation of standalone injection for reference. Just add 272 | this Swift package and you should be able to inject in the simulator. 273 | 274 | [InjectionNext](https://github.com/johnno1962/InjectionNext) is a 275 | currently experimental version of Injection that should be faster and 276 | more reliable for large projects. It integrates into a debugging flag of 277 | Xcode to find out how to recompile files to avoid parsing build logs 278 | and re-uses the client implementation of injection from `InjectionLite`. 279 | To use with external editors such as `Cursor`, InjectionNext can also 280 | use a file watcher to detect edits and fall back to build log parsing code. 281 | 282 | All these variations require you to add the "-Xlinker -interposble" linker flags 283 | for a Debug build or you will only be able to inject non-final methods of classes 284 | and all can be used in conjunction with either of the higher level 285 | [Inject](https://github.com/krzysztofzablocki/Inject) or 286 | [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI). 287 | 288 | ### Further information 289 | 290 | Consult the [old README](https://github.com/johnno1962/InjectionIII/blob/main/OLDME.md) which if anything contained 291 | simply "too much information" including the various environment 292 | variables you can use for customisation. A few examples: 293 | 294 | | Environment var. | Purpose | 295 | | ------------- | ------------- | 296 | | **INJECTION_DETAIL** | Verbose output of all actions performed | 297 | | **INJECTION_TRACE** | Log calls to injected functions (v4.6.6+) | 298 | | **INJECTION_HOST** | Mac's IP address for on-device injection | 299 | 300 | With an **INJECTION_TRACE** environment variable, injecting 301 | any file will add logging of all calls to functions and methods in 302 | the file along with their argument values as an aid to debugging. 303 | 304 | A little known feature of InjectionIII is that provided you have 305 | run the tests for your app at some point you can inject an 306 | individual XCTest class and have if run immediately – 307 | reporting if it has failed each time you modify it. 308 | 309 | ### Acknowledgements: 310 | 311 | This project includes code from [rentzsch/mach_inject](https://github.com/rentzsch/mach_inject), 312 | [erwanb/MachInjectSample](https://github.com/erwanb/MachInjectSample), 313 | [davedelong/DDHotKey](https://github.com/davedelong/DDHotKey) and 314 | [acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift) under their 315 | respective licenses. 316 | 317 | The 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. 318 | 319 | SwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook). 320 | See the project source and header file included in the app bundle 321 | for licensing details. 322 | 323 | This release includes a very slightly modified version of the excellent 324 | [canviz](https://code.google.com/p/canviz/) library to render "dot" files 325 | in an HTML canvas which is subject to an MIT license. The changes are to pass 326 | through the ID of the node to the node label tag (line 212), to reverse 327 | the rendering of nodes and the lines linking them (line 406) and to 328 | store edge paths so they can be coloured (line 66 and 303) in "canviz-0.1/canviz.js". 329 | 330 | It also includes [CodeMirror](http://codemirror.net/) JavaScript editor 331 | for the code to be evaluated using injection under an MIT license. 332 | 333 | The fabulous app icon is thanks to Katya of [pixel-mixer.com](http://pixel-mixer.com/). 334 | 335 | $Date: 2025/04/07 $ 336 | -------------------------------------------------------------------------------- /README_Chinese.md: -------------------------------------------------------------------------------- 1 | # InjectionIII.app Project 2 | 3 | ## 同时支持 Swift, Objective-C & C++ 的代码热重载工具! 4 | 5 | [英文版本 README](https://github.com/johnno1962/InjectionIII/blob/main/README.md) 6 | 7 | ![Icon](http://johnholdsworth.com/Syringe_128.png) 8 | 9 | Injection 能够让你在 iOS 模拟器、真机、Arm 芯片 Mac 直接运行的 iOS app 上无需重新构建或者重启你的 app 就实现更新 class 的实现、方法,添加 struct 或者 enum。节省开发者大量调试代码和设计迭代的时间。它把 Xcode 的职责从“源代码编辑器”变成“程序编辑器”,源码的修改不再仅仅保存在磁盘中而是会直接注入到运行时的程序中 10 | ### 重要提示:Injection 与 Xcode 16.3 11 | 12 | InjectionIII 的工作原理是将编辑过的源文件重新编译成动态库,然后加载到你的应用程序中。它通过搜索最近的 Xcode 构建日志中的 `swift-frontend` 编译器调用来确定如何重新编译文件。不幸的是,在这个功能正常工作了 10 年之后,Xcode 16.3 默认不再记录这些信息。但是,如果你使用 "Editor/Add Build Setting/Add User-Defined Setting" 为项目的 `Debug` 构建设置添加 `EMIT_FRONTEND_COMMAND_LINES` 值(设置为 "YES"),那么 InjectionIII 可以继续像以前一样工作。 13 | 14 | 15 | ### 如何使用 16 | 17 | 你可以在 [github 18 | releases](https://github.com/johnno1962/InjectionIII/releases) 下载最新的 app 19 | 也可以选择通过 [Mac App Store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12) 下载,然后你需要把下面这些代码添加到你的工程中并且在 app 启动时执行它(比如在 didFinishLaunchingWithOptions 的时候),这些配置工作就完成了。 20 | 21 | 22 | ```Swift 23 | #if DEBUG 24 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() 25 | //for tvOS: 26 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load() 27 | //Or for macOS: 28 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load() 29 | #endif 30 | ``` 31 | 另外一个非常重要的事情是添加 `-Xlinker` and `-interposable` 这两个参数到 "Other Linker Flags" 你的工程文件中(注意只修改 `Debug` 配置如下图所示) 32 | 33 | ![Icon](interposable.png) 34 | 35 | 配置完成以后,当 app 运行起来以后控制台会输出一条关于文件监视器监听目录的消息,当前工程中包含的源文件保存的同时它也会同时被注入到您的设备中。所有旧的代码实现都会被替换为最新的代码实现 36 | 37 | 通常来说,你想要在屏幕中立刻看到最新的效果,可能需要让某些函数被重新调用一次。比如你在 view controller 中注入了代码,想要让它被重新渲染。你可以实现 `@objc func injected()` 方法,这个函数将会被框架自动调用。在项目中使用可以参考下面这个样例代码 38 | 39 | ```Swift 40 | #if DEBUG 41 | extension UIViewController { 42 | @objc func injected() { 43 | viewDidLoad() 44 | } 45 | } 46 | #endif 47 | ``` 48 | 另外一个解法是用 "hosting",使用的是 49 | [Inject](https://github.com/krzysztofzablocki/Inject) 这个 Swift Package,用法参考[这篇博客](https://merowing.info/2022/04/hot-reloading-in-swift/). 50 | 51 | ### 哪些做不到的? 52 | 53 | 你不能修改数据在内存中的布局,比如你不能添加、删除、排序属性。对于非最终类(non-final classes),增加或删除方法也是不能工作的,因为用于分派的虚表(vtable)本身就是一种数据结构,不能靠 Injection 修改。Injection 也无法判断哪些代码段需要重新执行以更新显示,如上所述你需要自己判断。此外,不要过度使用访问控制。私有属性和方法不能直接被注入,特别是在扩展中,因为它们不是全局可替换的符号。它们通常通过间接方式进行注入,因为它们只能在被注入的文件内部访问,但这可能会引起混淆。最后,代码注入的同时又在对源文件执行添加、重命名或删除的操作可能会出问题。您可能需要重新构建并重新启动您的应用程序,甚至关闭并重新打开您的项目以清除旧的 Xcode 构建日志。 54 | 55 | ### Injection 在 SwiftUI 中的使用 56 | 57 | 如果说有什么区别的话,SwiftUI 比 UIKit 更适合注入,因为它有特定的机制来更新显示,但你需要对每个想要注入的 `View` 结构体做一些修改。为了强制重新绘制,最简单的方法是添加一个属性来观察注入何时发生: 58 | 59 | ``` 60 | @ObserveInjection var forceRedraw 61 | ``` 62 | 这个属性包装器可以在 [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) 或 [Inject](https://github.com/krzysztofzablocki/Inject) Swift 包(SMP)中找到。它实际上就是包含了一个 @Published 整数,视图可以观察这个整数,它会在每次注入时递增。您可以使用以下任一方法保证相关的代码在整个项目中可用: 63 | 64 | ``` 65 | @_exported import HotSwiftUI 66 | or 67 | @_exported import Inject 68 | ``` 69 | 70 | 让 SwiftUI 注入所需的第二个更改是调用 View 的 `.enableInjection()` 方法将 body 属性的返回类型“擦除”为 `AnyView` ——这个技巧叫做"erase the return type"。这是因为,在添加或删除 SwiftUI 元素时,body 属性的具体返回类型可能会发生变化,这相当于内存布局的更改,可能会导致崩溃。总的来说,每个 body 的末尾都应该看起来像这样: 71 | 72 | ``` 73 | var body: some View { 74 | VStack or whatever { 75 | // Your SwiftUI code... 76 | } 77 | .enableInjection() 78 | } 79 | 80 | @ObserveInjection var redraw 81 | ``` 82 | 你可以保留这些修改到生产环境中,`Release` 构建时这个调用会被优化为一个无操作(no-op) 83 | 84 | #### Xcode 16 85 | Xcode 16 中新增了 SWIFT_ENABLE_OPAQUE_TYPE_ERASURE 构建设置。这个设置默认是开启的,你不再需要显式地擦除视图的 body。但是,你仍然需要使用 `@ObserveInjection` 来强制重新绘制。 86 | 87 | 更多的信息可以参考 [Xcode 16.2 release notes](https://developer.apple.com/documentation/xcode-release-notes/xcode-16_2-release-notes). 88 | 89 | ### 关于 Injection 在 iOS, tvOS or visionOS 设备上的运行 90 | [github 91 | 4.8.0+ releases](https://github.com/johnno1962/InjectionIII/releases) 版本以上的 InjectionIII.app 需要通过修改 user default 并且重启 mac 端的 InjectionIII.app 明确表示需要真机调试,在命令行执行下面代码可以修改 user default 92 | 93 | ``` 94 | $ defaults write com.johnholdsworth.InjectionIII deviceUnlock any 95 | ``` 96 | 97 | 还需要在在“Build Phase” 添加一个 run script, 并且关闭 "User Script Sandboxing" 98 | 99 | ``` 100 | RESOURCES=/Applications/InjectionIII.app/Contents/Resources 101 | if [ -f "$RESOURCES/copy_bundle.sh" ]; then 102 | "$RESOURCES/copy_bundle.sh" 103 | fi 104 | ``` 105 | 最后在 app 启动以后加载相关的 bundle,具体参考下面的示例代码 106 | 107 | ``` 108 | #if DEBUG 109 | if let path = Bundle.main.path(forResource: 110 | "iOSInjection", ofType: "bundle") ?? 111 | Bundle.main.path(forResource: 112 | "macOSInjection", ofType: "bundle") { 113 | Bundle(path: path)!.load() 114 | } 115 | #endif 116 | ``` 117 | 这样配置以后,模拟器和真机就都可以工作了。有关如何通过 Wi-Fi 连接到 InjectionIII.app 进行调试的详细信息,请查阅 [HotReloading project](https://github.com/johnno1962/HotReloading) 项目 的 README。你还需要从下拉菜单中手动选择项目目录以供文件监视器使用。 118 | 119 | ### 在 macOS 上工作 120 | macOS 也可以工作,但在开发过程中,你需要暂时关闭 "app sandbox" 和 "hardened runtime-library validation" ,以便能够动态加载代码。为了避免代码签名问题,请按照上述在真实设备上进行注入的说明,使用新的 `copy_bundle.sh` 脚本。 121 | 122 | ### 工作原理 123 | 124 | Injection 这个项目这么多年来尝试了各种各样的方式来实现,最开始使用的是 Objective-C 的 "Swizzling" API,现在则选择苹果链接器的一个特性—— "interposing" 来实现,它让 Swift (动态注入)方法或计算属性成为可能。 125 | 126 | 当你的代码在Swift中调用一个函数时,它通常是“statically 127 | dispatched”(静态分派)的,即通过被调用函数的“mangled symbol”(混淆符号)进行链接。然而,当你使用“-interposable”选项链接应用程序时,会添加一个额外的间接层,它通过一段可写的内存区域找到所有被调用函数的地址。利用操作系统层面提供的加载可执行代码的能力以及 [fishhook](https://github.com/facebook/fishhook) 库来“rebind”(重新绑定)函数调用,因此可以“interpose”(插入)任何函数的新实现,并在运行时有效地将它们缝合到你的程序的其余部分。从这一点开始,它将表现得就像新代码已经被构建进了程序中。 128 | 129 | Injection 使用 `FSEventSteam` API 来监视源文件何时发生变化,并扫描最后一次 Xcode build log(构建日志)得到如何重新编译它,并链接一个可以加载到你的程序中的动态库的信息。然后,运行时支持会加载这个动态库,并扫描它包含的函数定义,并将它们“interposes”(插入)到程序的其余部分。此外,因为 “non-final” (没有被声明为 final 的)类方法的分派使用了一个“vtable”(虚表,类似于C++中的虚拟方法),这也需要更新,这个项目也已经把这个问题处理好了,当然也包括以及任何遗留的 Objective-C “Swizzling”。 130 | 131 | 如果您对 Injection 的工作原理感兴趣,最好的信息来源是我的书《[Swift Secrets](http://books.apple.com/us/book/id1551005489)》或者直接阅读 [InjectionLite](https://github.com/johnno1962/InjectionLite) Swift 包(SMP)中的源码。关于“interposing”的更多信息,可以参考这篇博客文章 [Dyld Dynamic Linking on OS X](https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html) 或者 [fishhook](https://github.com/facebook/fishhook) 项目的 README。关于应用程序本身的组织结构,可以参考[ROADMAP.md](https://github.com/johnno1962/InjectionIII/blob/main/ROADMAP.md)文件。 132 | 133 | ### 关于术语的说明 134 | 135 | Injection 工作有三个组成部分,这三者协同工作让整个项目能够工作 136 | 1. FileWatcher:文件监视器,检测代码文件变动情况 137 | 1. 动态库生成:将更改的代码文件重新编译成动态库 138 | 1. Injection 代码本身:负责在应用程序运行时将最新代码缝合到应用中 139 | 140 | "Injection classic" 是最经典的使用方式。先运行从 GitHub 下载的 [二进制发行版](https://github.com/johnno1962/InjectionIII/releases) 中包含的 InjectionIII.app。然后在模拟器运行你的项目时,使用 NSBundle 的 load 方法加载 `/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle` 到你的程序中。在这种使用方式下,文件监视和源代码重新编译是在 mac 端 app 内部完成的,接着模拟器中的 bundle 通过套接字(socket)与 mac 连接来得知何时有新的动态库准备加载。 141 | 142 | "App Store injection" 是指在 Appstore 下载 injection.app,这种版本的应用是沙盒化的,虽然文件监视器仍然在应用内部运行,但是重新编译和加载的工作被委托到模拟器内部执行。这种方式有些 C 语言的头文件会产生一些错误,这是由于模拟器使用大小写敏感的文件系统——这模拟了真实设备的文件系统。 143 | 144 | "HotReloading injection" 可以让你在真实设备(你的 iPhone 或者 iPad)上使用本项目。因为手机访问不到 Mac 文件系统的 bundle 文件,因此你可以将 [HotReloading Swift Package](https://github.com/johnno1962/HotReloading) 添加到你的项目中(仅在开发期间!),HotReloading 里的 bundle 包含本项目执行动态加载的所有代码。你必须把使用未沙盒化(un-sandboxed)的二进制发行版。上面曾经提及的 `copy_bundle.sh` 脚本也不用再添加到 `Build Phase` 中了 145 | 146 | “独立式版本 Injection”(Standalone injection)是本项目的最新演变,在模拟器运行时使用时会更加便捷。这种方式下 mac 端的 Injection.app 不需要再运行了,简单地加载其中一个 bundle,文件监视、重新编译和注入都在模拟器内部完成。默认情况下,这会监视你主目录下任何 Swift 文件的更改,不过你可以使用环境变量`INJECTION_DIRECTORIES`来更改这个设置。 147 | 148 | [InjectionLite](https://github.com/johnno1962/InjectionLite) 是一个只包含了注入相关的核心代码,代码量相对较少。只需添加这个 Swift 包,你应该就能够在模拟器中进行注入,你可以通过这个项目学习到 Injection 的工作原理。 149 | 150 | [InjectionNext](https://github.com/johnno1962/InjectionNext) 是 Injection 的一个目前正在实验的版本,它致力于为大型项目提供更快、更可靠的性能。它集成到 Xcode 的调试标志(debugging flag)中,以了解如何重新编译文件,从而避免了解析构建日志(build logs),客户端的实现代码直接复用的是 `InjectionLite` 项目里的代码。为了让诸如 `Cursor` 等外部编辑器也可以工作,InjectionNext 也同样支持文件监视器来检测编辑,这时候工作方式将回退到通过构建日志(build log)来解析代码。 151 | 152 | 153 | 所有这些变体版本都需要你在 Debug 构建选项 "Other Linker Flags" 中添加 "-Xlinker -interposable" 链接器标志,否则你将只能为非final类的注入方法。除此之外,还有一些更高层次的封装项目 [Inject](https://github.com/krzysztofzablocki/Inject) 和 154 | [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) ,都可以和本项目结合起来一起使用。 155 | 156 | ### 更多信息 157 | 158 | 请阅读旧的[old README](https://github.com/johnno1962/InjectionIII/blob/main/OLDME.md) 文件,以获取更多你想要需要了解的信息,包括你可以用于自定义的各种环境变量。以下是一些例子: 159 | 160 | | 环境变量(Environment var) | 目的(Purpose)| 161 | | ------------- | ------------- | 162 | | **INJECTION_DETAIL** | 更详细的日志输出 | 163 | | **INJECTION_TRACE** | 注入方法被执行的信息 (4.6.6+ 以后的版本才能支持) | 164 | | **INJECTION_HOST** | 真机注入时填写 Mac 端 IP address | 165 | 166 | 通过设置一个 **INJECTION_TRACE** 环境变量,注入任何文件都会添加对该文件中所有函数和方法的调用日志,以及它们的参数值,以帮助调试。 167 | 168 | InjectionIII 另一个鲜为人知的特性是,只要你的测试用例执行过一次,你就可以注入一个单独的 XCTest 类,并立即运行它——每次你修改它时,都会报告它是否失败。 169 | 170 | ### 致谢: 171 | 172 | 本项目包含的代码包括 [rentzsch/mach_inject](https://github.com/rentzsch/mach_inject), 173 | [erwanb/MachInjectSample](https://github.com/erwanb/MachInjectSample), 174 | [davedelong/DDHotKey](https://github.com/davedelong/DDHotKey) and 175 | [acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift) 并且遵循了相关许可 176 | 177 | App Tracing 工作的原理是使用了 [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) 提供的 trampoline 实现并通过一个遵循了 MIT 许可证的 [SwiftTrace](https://github.com/johnno1962/SwiftTrace) 项目来实现的 178 | 179 | SwiftTrace 使用了非常方便的 [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook)。 180 | 本项目具体遵循的许可证(licensing)在 app bundle 中源码头文件中都有具体的说明 181 | 182 | 这个版本包含了一个经过轻微修改的优秀[canviz](https://code.google.com/p/canviz/) 库,用于在 HTML 画布上渲染 “dot” 文件,该库遵循 MIT 许可证。所做的修改包括:将节点的ID传递给节点标签标签(第 212 行),反转节点的渲染以及连接它们的线的渲染(第 406 行),以及存储边路径以便它们可以被着色(第 66 行和第 303 行)在“canviz-0.1/canviz.js”中。 183 | 184 | 同时还包括了[CodeMirror](http://codemirror.net/),这是一个 JavaScript 编辑器 185 | 用于在 MIT 许可证下评估将要通过注入执行的代码。 186 | 187 | 这个出色的应用程序图标要感谢 Katya 提供的[pixel-mixer.com](http://pixel-mixer.com/)的 188 | 189 | 190 | $Date: 2025/05/12 $ 191 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | 2 | ## InjectionIII Roadmap 3 | 4 | This isn't a roadmap of future directions as, in a sense, the project is already "there". 5 | Just a locator for the key files and their role implementing "live code injection" for users. 6 | 7 | The InjectionIII project is largely a shell project now containing a lot of interesting code 8 | that is no longer used. The key sources files are brought in from the [HotReloading](https://github.com/johnno1962/HotReloading) Swift package which 9 | is able to build both the app as a daemon and client iOS project support code 10 | which is normally packaged as the iOSInjection.bundle in the app releases. 11 | HotReloading in turn brings in [SwiftTrace](https://github.com/johnno1962/SwiftTrace) 12 | which contains most of the infrastructure you need for injection such as the ability 13 | associate a function pointer with a symbol name and de-mangle that into the description 14 | of a Swift type member. It also allows you to scan the symbol table of a newly dlopen'd 15 | dynamic library image to look for all classes, types and functions it contains that should 16 | be injected using the symbol suffix. 17 | 18 | [HotReloading/AppDelegate.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/AppDelegate.swift): The app delegate of the menu bar application/daemon that chiefly looks after setting up a [FileWatcher.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/FileWatcher.swift) instance 19 | for the selected project that looks for modifications to source files that should be recompiled 20 | and injected. More experimental features such as tracing are in the AppDelegate extension in 21 | [Experimental.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/Experimental.swift). 22 | The AppDelegate has to cater for three configurations: The Sandboxed App Store 23 | releaes of the app, the binary [github releases](https://github.com/johnno1962/InjectionIII/releases) 24 | and when it is run as a daemon from a "Build Phase" using the HotReloading project. 25 | 26 | [HotReloading/InjectionServer.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/InjectionServer.swift): listens 27 | on localhost for sockets connections from client apps and sends them commands to inject 28 | modified source files when they are saved. 29 | 30 | [HotReloading/SwiftEval.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloading/SwiftEval.swift): Standalone 31 | source which looks after the recompilation of a source file and linking the resulting object file 32 | into a dynamic library that can be loaded. It works out the Swift compiler command to do 33 | this by "grepping" (using perl) the compressed build logs in the current project's DerivedData. 34 | An instance of the class runs in the simulator for the Sandboxed version of the app and in the 35 | main app process for the binary github releases. 36 | 37 | [HotReloading/SwiftInjection.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloading/SwiftInjection.swift): After 38 | the dynamic library prepared by SwiftEval.swift has been dlopen()'d this file sets about the actual 39 | injection of the new implementations into the client app. It does this three ways. For 40 | Objective-C methods it "Swizzles" the new implementations on top of the old using 41 | Objective-C runtime apis. For Swift classes it scans class information, the later 42 | part of which is a vtable of member function pointers and "patches" in the new function 43 | pointers. For value types, statics and top level functions it scans the symbol table of the 44 | dynamically loaded image for symbols that are functions (using their distinct suffixes) and 45 | uses "interposing" (a dynamic linker feature used to bind system symbols) to rebind the 46 | main application bundle to use the new implementations using a unique piece of C code 47 | called [fishhook](https://github.com/johnno1962/fishhook). In order for this to work an app 48 | needs to have been linked with the option "-interposable" which makes all function calls to 49 | global symbols indirect through a patchable pointer as described 50 | [here](https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html). 51 | 52 | A new final part of injecting a newly compiled source file is the "reverse interpose" of the 53 | "mutable accessors" for top level and static variables which redirects newly injected code to 54 | take their value from the main app bundle rather than have them reinitialise with each injection. 55 | 56 | [HotReloading/UnhidingEval.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloading/UnhidingEval.swift). This was introduced as a means of overriding functionality in SwiftEval.swift without making it 57 | dependant on the rest of the project. Contains a pre-Xcode13 fix for default argument 58 | generators which was preventing some files from being injectable, along with other 59 | fixes to tailor Xcode 13 compilation commands to only compile a single file at a time. 60 | 61 | [HotReloading/SwiftSweeper.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloading/SwiftSweeper.swift): Implements 62 | a sweep of an application to search for live instances of classes that have just been injected 63 | to implement the `@objc func injected()` method you can use to refresh a display for 64 | example when say, a view controller is injected. 65 | 66 | [HotReloading/InjectionClient.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloading/InjectionClient.swift): An 67 | instance of this class connects to the InjectionIII app or daemon and receives commands 68 | to compile/load dynamic libraries and inject them. It also has to delegate to the app 69 | the codesigning of the dynamic library. 70 | 71 | [HotReloading/ClientBoot.mm](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloadingGuts/ClientBoot.mm): Contains 72 | remaining code that can't be conveniently expressed in Swift in particular a `+load` method 73 | to instantiate an InjectionClient.swift object to connect automatically to the app/daemon. 74 | 75 | [HotReloading/SimpleSocket.mm](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloadingGuts/SimpleSocket.mm). I 76 | draw the line at trying to do BSD socket programming in Swift so this is my Objective-C 77 | client/server abstraction of which InjectionServer.swift and InjectionClient.swift are subclasses. 78 | 79 | [HotReloading/UnHide.mm](https://github.com/johnno1962/HotReloading/blob/main/Sources/HotReloadingGuts/Unhide.mm): The ageing 80 | implementation of the "unhiding" functionality built into the app which is headed for 81 | deprecation since Xcode 13 handles default arguments differently. 82 | 83 | [HotReloading/SignerService.mm](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiondGuts/SignerService.m) An 84 | embarrassingly old piece of code which looks after codesigning the dylib so it can be loaded 85 | in the simulator. For the HotReloading daemon version of InjectionIII run from a build phase 86 | it has access to the build environment variables of the project from which it can take the 87 | signing identity. 88 | 89 | [HotReloading/DeviceServer.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/DeviceServer.swift). A 90 | subclass of InjectionServer.swift that runs in the daemon process that supports injection 91 | on a real device. To do this it maintains a pointer to an empty buffer of executable nothing 92 | on the client device from the framework package [InjectionScratch](https://github.com/johnno1962/InjectionScratch) 93 | into which the dylib can be written (while debugging) rather than dynamically loaded and 94 | then made executable after simulating as much as is possible of the tasks of an actual 95 | dynamic load/linking. After this, it is "injected" into the app in the way described above. 96 | 97 | [HotReloading/StandaloneInjection.swift](https://github.com/johnno1962/HotReloading/blob/main/Sources/injectiond/StandaloneInjection.swift): 98 | A startover implementation of injection for use in the simulator with the 99 | HotReloading project which removes the need for the App itself. 100 | 101 | 102 | $Date: 2022/04/09 $ 103 | 104 | -------------------------------------------------------------------------------- /SwiftEval/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftEval 4 | // 5 | // Created by John Holdsworth on 02/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let evalGlobal = "GLOBAL" 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | // Override point for customization after application launch. 21 | let splitViewController = window!.rootViewController as! UISplitViewController 22 | let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController 23 | navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem 24 | splitViewController.delegate = self 25 | // Bundle(path: "/Applications/Injection.app/Contents/Resources/InjectionLoader.bundle")?.load() 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 32 | } 33 | 34 | func applicationDidEnterBackground(_ application: UIApplication) { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | func applicationWillEnterForeground(_ application: UIApplication) { 40 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | func applicationDidBecomeActive(_ application: UIApplication) { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | func applicationWillTerminate(_ application: UIApplication) { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | // MARK: - Split view 52 | 53 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { 54 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 55 | guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } 56 | if topAsDetailController.detailItem == nil { 57 | // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. 58 | return true 59 | } 60 | return false 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /SwiftEval/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftEval/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftEval/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /SwiftEval/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // SwiftEval 4 | // 5 | // Created by John Holdsworth on 02/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | @IBOutlet weak var detailDescriptionLabel: UILabel! 14 | 15 | 16 | func configureView() { 17 | // Update the user interface for the detail item. 18 | if let detail = detailItem { 19 | if let label = detailDescriptionLabel { 20 | label.text = detail.description 21 | } 22 | } 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do any additional setup after loading the view, typically from a nib. 28 | configureView() 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | var detailItem: NSDate? { 37 | didSet { 38 | // Update the view. 39 | configureView() 40 | } 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /SwiftEval/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarTintParameters 32 | 33 | UINavigationBar 34 | 35 | Style 36 | UIBarStyleDefault 37 | Translucent 38 | 39 | 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | NSAppTransportSecurity 55 | 56 | NSAllowsArbitraryLoads 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SwiftEval/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // SwiftEval 4 | // 5 | // Created by John Holdsworth on 02/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MasterViewController: UITableViewController { 12 | 13 | var detailViewController: DetailViewController? = nil 14 | var objects = [Any]() 15 | 16 | @objc func injected() { 17 | print("I've been injected!") 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any additional setup after loading the view, typically from a nib. 23 | navigationItem.leftBarButtonItem = editButtonItem 24 | 25 | let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:))) 26 | navigationItem.rightBarButtonItem = addButton 27 | if let split = splitViewController { 28 | let controllers = split.viewControllers 29 | detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController 30 | } 31 | } 32 | 33 | override func viewWillAppear(_ animated: Bool) { 34 | clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed 35 | super.viewWillAppear(animated) 36 | } 37 | 38 | override func didReceiveMemoryWarning() { 39 | super.didReceiveMemoryWarning() 40 | // Dispose of any resources that can be recreated. 41 | } 42 | 43 | @objc 44 | func insertNewObject(_ sender: Any) { 45 | objects.insert(NSDate(), at: 0) 46 | let indexPath = IndexPath(row: 0, section: 0) 47 | tableView.insertRows(at: [indexPath], with: .automatic) 48 | } 49 | 50 | // MARK: - Segues 51 | 52 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 53 | if segue.identifier == "showDetail" { 54 | if let indexPath = tableView.indexPathForSelectedRow { 55 | let object = objects[indexPath.row] as! NSDate 56 | let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController 57 | controller.detailItem = object 58 | controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem 59 | controller.navigationItem.leftItemsSupplementBackButton = true 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Table View 65 | 66 | override func numberOfSections(in tableView: UITableView) -> Int { 67 | return 1 68 | } 69 | 70 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return objects.count 72 | } 73 | 74 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 76 | 77 | let object = objects[indexPath.row] as! NSDate 78 | cell.textLabel!.text = object.description 79 | print(eval("\\(self.title!)")) 80 | print(eval("\\(evalGlobal)")) 81 | print(eval("12345")) 82 | return cell 83 | } 84 | 85 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 86 | // Return false if you do not want the specified item to be editable. 87 | return true 88 | } 89 | 90 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 91 | if editingStyle == .delete { 92 | objects.remove(at: indexPath.row) 93 | tableView.deleteRows(at: [indexPath], with: .fade) 94 | } else if editingStyle == .insert { 95 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 96 | } 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /SwiftEval/SwiftEval.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftEvalTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftEvalTests/SwiftEvalTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftEvalTests.swift 3 | // SwiftEvalTests 4 | // 5 | // Created by John Holdsworth on 02/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SwiftEvalTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | XCTAssertEqual("123", swiftEvalString(contents: "123"), "Basic eval test") 27 | XCTAssertEqual(123, swiftEval("123", type: Int.self), "Basic eval test") 28 | } 29 | 30 | func testPerformanceExample() { 31 | // This is an example of a performance test case. 32 | self.measure { 33 | // Put the code you want to measure the time of here. 34 | XCTAssertEqual("1234", swiftEvalString(contents: "1234"), "eval performance test") 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /SwiftUISupport/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2020 John Holdsworth. All rights reserved. 23 | NSPrincipalClass 24 | SwiftUISupport 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftUISupport/SwiftUISupport-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUISupport-Bridging-Header.h 3 | // InjectionIII 4 | // 5 | // Created by John Holdsworth on 25/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #ifndef SwiftUISupport_Bridging_Header_h 10 | #define SwiftUISupport_Bridging_Header_h 11 | 12 | #import "../SwiftTrace/SwiftTraceGuts/include/SwiftTrace.h" 13 | 14 | #endif /* SwiftUISupport_Bridging_Header_h */ 15 | -------------------------------------------------------------------------------- /SwiftUISupport/SwiftUISupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUISupport.swift 3 | // SwiftUISupport 4 | // 5 | // Created by John Holdsworth on 25/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/ResidentEval/SwiftUISupport/SwiftUISupport.swift#26 $ 9 | // 10 | 11 | import SwiftUI 12 | import SwiftTrace 13 | 14 | /// Add conformances for types that contain floats 15 | extension SwiftUI.EdgeInsets: SwiftTraceFloatArg {} 16 | extension SwiftUI.UnitPoint: SwiftTraceFloatArg {} 17 | extension SwiftUI.Angle: SwiftTraceFloatArg {} 18 | 19 | /// generic function to find the Binding type for a wrapped type 20 | public func getBindingType(value: Type, out: inout Any.Type?) { 21 | if !SwiftMeta.structsPassedByReference.contains(autoBitCast(Type.self)) { 22 | out = SwiftUI.Binding.self 23 | } 24 | } 25 | 26 | /// generic function to find the Binding type for a wrapped type 27 | public func getStateType(value: Type, out: inout Any.Type?) { 28 | out = SwiftUI.State.self 29 | } 30 | 31 | @objc(SwiftUISupport) 32 | class SwiftUISupport: NSObject { 33 | 34 | @objc class func setup(pointer: UnsafeMutableRawPointer?) { 35 | if let swiftUIPath = swiftUIBundlePath() { 36 | _ = SwiftMeta.structsPassedByReference 37 | SwiftMeta.process(bundlePath: swiftUIPath, 38 | problemTypes: &SwiftMeta.structsPassedByReference) 39 | } 40 | SwiftMeta.wrapperHandlers["SwiftUI.Binding<"] = 41 | SwiftMeta.bindGeneric(name: "getBindingType", owner: Self.self) 42 | SwiftMeta.wrapperHandlers["SwiftUI.State<"] = 43 | SwiftMeta.bindGeneric(name: "getStateType", owner: Self.self) 44 | SwiftTrace.makeTraceable(types: [SwiftUI.Text.self]) 45 | print("💉 Installed SwiftUI type handlers") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /interposable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/InjectionIII/3de91eea594329558dc863268fbf9fcfdb3a760e/interposable.png -------------------------------------------------------------------------------- /signer/SignerService.h: -------------------------------------------------------------------------------- 1 | // 2 | // SignerService.h 3 | // InjectionIII 4 | // 5 | // Created by John Holdsworth on 06/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "SimpleSocket.h" 10 | 11 | @interface SignerService : SimpleSocket 12 | 13 | + (BOOL)codesignDylib:(NSString * _Nonnull)dylib identity:(NSString * _Nullable)identity; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /signer/SignerService.m: -------------------------------------------------------------------------------- 1 | // 2 | // SignerService.m 3 | // InjectionIII 4 | // 5 | // Created by John Holdsworth on 06/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/ResidentEval/signer/SignerService.m#20 $ 9 | // 10 | 11 | #import "SignerService.h" 12 | 13 | @implementation SignerService 14 | 15 | + (BOOL)codesignDylib:(NSString *)dylib identity:(NSString *)identity { 16 | static NSString *adhocSign = @"-"; 17 | const char *envIdentity = getenv("CODE_SIGN_IDENTITY"); 18 | const char *toolchainDir = getenv("TOOLCHAIN_DIR") ?: 19 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain"; 20 | if (envIdentity) { 21 | identity = [NSString stringWithUTF8String:envIdentity]; 22 | NSLog(@"Using CODE_SIGN_IDENTITY: %@", identity); 23 | } 24 | NSString *command = [NSString stringWithFormat:@"" 25 | "(export CODESIGN_ALLOCATE=\"%s/usr/bin/codesign_allocate\"; " 26 | "if /usr/bin/file \"%@\" | grep ' bundle ' >/dev/null;" 27 | "then /usr/bin/codesign --force -s \"%@\" \"%@\";" 28 | "else exit 1; fi)", 29 | toolchainDir, dylib, identity ?: adhocSign, dylib]; 30 | return system(command.UTF8String) >> 8 == EXIT_SUCCESS; 31 | } 32 | 33 | - (void)runInBackground { 34 | char __unused skip, buffer[1000]; 35 | buffer[read(clientSocket, buffer, sizeof buffer-1)] = '\000'; 36 | NSString *path = [[NSString stringWithUTF8String:buffer] componentsSeparatedByString:@" "][1]; 37 | 38 | if ([[self class] codesignDylib:path identity:nil]) { 39 | snprintf(buffer, sizeof buffer, "HTTP/1.0 200 OK\r\n\r\n"); 40 | write(clientSocket, buffer, strlen(buffer)); 41 | } 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /signer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // signer 4 | // 5 | // Created by John Holdsworth on 03/11/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "SignerService.h" 10 | 11 | int main(int argc, const char *argv[]) { 12 | [SignerService runServer:@":8899"]; 13 | return 0; 14 | } 15 | --------------------------------------------------------------------------------