├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
4 |
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 |
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------