├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── cupcake.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ └── cupcake.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Package.swift
├── README.md
└── Sources
└── Jinx
├── Core
├── Rebind.swift
├── Replace.swift
└── Substrate.swift
├── Extensions
└── String.swift
├── Helpers
├── Ivar.swift
├── JinxPreferences.swift
├── Lock.swift
└── Storage.swift
├── Protocols
├── Hook.swift
├── HookFunc.swift
└── HookGroup.swift
└── Types
├── RebindHook.swift
└── Table.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | .theos
7 | /packages
8 | .theos/
9 | packages/
10 | .swiftpm
11 | .swiftpm/
12 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcuserdata/cupcake.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Paisseon/Jinx/9fe038cb92b3edbcd4daf2a7f7aff55002b609fa/.swiftpm/xcode/package.xcworkspace/xcuserdata/cupcake.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcuserdata/cupcake.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Jinx.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | Jinx
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.8
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Jinx",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "Jinx",
12 | targets: ["Jinx"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "Jinx",
23 | dependencies: [])
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jinx
2 | *Pure Swift tweak development library for iOS and macOS*
3 |
4 | Jinx is a library for tweak developers to write their tweaks in Swift.
5 |
6 | ## Prerequisites
7 | - macOS device with Xcode 14 or newer
8 | - (for using templates) Theos
9 |
10 | ## Features
11 | - Struct-based hooks for ObjC messages and C functions
12 | - Fearless speculative and dynamic hooking
13 | - Supports batched hooking for ObjC messages
14 | - Written in 100% Swift and Assembly
15 | - No preprocessors— better for Xcode and fast compilation
16 | - First tweak framework with SPM support =)
17 | - Small, fast, and lightweight
18 | - Easy templates for use with Theos (prefs or no prefs)
19 | - Works on rootless even from non-rootless branches
20 | - Includes preferences reader
21 |
22 | ## Installation
23 | ### Theos
24 | 1. Download the latest archive from Releases
25 | 2. Extract and run `python3 install_jinx.py`
26 | ### Without Theos
27 | 1. Simply add this repo as a dependency to your Package.swift file
28 |
29 | ## Usage
30 | ### Activating hooks
31 | In the provided Tweak struct’s `ctor` function, initialise an instance of your hook, then run its `.hook()` function:
32 |
33 | ```swift
34 | ExampleHook().hook()
35 | ExampleHookGroup().hook()
36 | ExampleHookFunc().hook()
37 | ```
38 |
39 | ### Hooking an ObjC message
40 | ```swift
41 | struct ExampleHook: Hook {
42 | typealias T = @convention(c) (AnyObject, Selector) -> Bool
43 |
44 | let cls: AnyClass? = objc_lookUpClass("SomeClass")
45 | let sel: Selector = sel_registerName("someMethod")
46 | let replace: T = { _, _ in true }
47 | }
48 | ```
49 |
50 | ### Hooking multiple ObjC messages in a single class
51 | One `HookGroup`-conforming struct can hold `T0`-`T9` and corresponding `sel` and `replace` properties.
52 |
53 | ```swift
54 | struct ExampleHookGroup: HookGroup {
55 | typealias T0 = @convention(c) (AnyObject, Selector) -> Int
56 | typealias T1 = @convention(c) (AnyObject, Selector) -> String
57 |
58 | let cls: AnyClass? = objc_lookUpClass("SomeClass")
59 |
60 | let sel0: Selector = sel_registerName("firstMethod")
61 | let sel1: Selector = sel_registerName("secondMethod")
62 |
63 | let replace0: T0 = { _, _ in 413 }
64 | let replace1: T1 = { _, _ in "EMT!" }
65 |
66 | ```
67 |
68 | ### Hooking a C, C++, or Swift function
69 | ```swift
70 | struct ExampleHookFunc: HookFunc {
71 | typealias T = @convention(c) (Int) -> Void
72 |
73 | let name: String = "someFunc"
74 | let image: String? = "/usr/lib/system/libsomething.dylib"
75 | let replace: T = { _ in NSLog("Hej fra someFunc!") }
76 | }
77 | ```
78 |
79 | ### Calling original code
80 | For `Hook` and `HookFunc`, simply call `orig()` from within the replacement definition and pass the requisite parameters. For example, `orig(obj, sel)`.
81 |
82 | `HookGroup` uses `orig0` through `orig9`. These are identical to the other protocols’ `orig` except for the name, which allows unambiguity as to which sub-hook it relates.
83 |
84 | ### Getting and setting instance variables
85 | Inside a `Hook` or `HookGroup` replacement definition,
86 |
87 | ```swift
88 | Ivar.get("ivarName", for: obj) // returns an optional
89 | Ivar.set("ivarName", for: obj, to: 413)
90 | ```
91 |
92 | ### Dynamic hooking
93 | To use dynamic hooking (i.e., hooking a class and method determined at runtime), you can abstain from assigning a value to cls and/or sel in the struct definition and instead assign it during initialisation.
94 |
95 | The same principle goes for `HookGroup` and `HookFunc` with their respective properties.
96 |
97 | ```swift
98 | ExampleHook(cls: objc_lookUpClass(someString), sel: sel_registerName(anotherString)).hook()
99 | ```
100 |
101 | ### Reading preferences
102 | Preferences are stored in the JinxPreferences struct. Simply create an instance of this struct and use `let isEnabled: Bool = prefs.get(for: "isEnabled", default: true)`
--------------------------------------------------------------------------------
/Sources/Jinx/Core/Rebind.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rebind.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 21/03/2023.
6 | //
7 |
8 | import MachO
9 |
10 | struct Rebind {
11 | let hook: RebindHook
12 |
13 | func rebind() -> Bool {
14 | for i: UInt32 in 0 ..< _dyld_image_count() {
15 | guard let header: UnsafePointer = _dyld_get_image_header(i) else {
16 | continue
17 | }
18 |
19 | let machHeader: UnsafePointer = header.withMemoryRebound(to: mach_header_64.self, capacity: 1) { $0 }
20 | let slide: Int = _dyld_get_image_vmaddr_slide(i)
21 |
22 | guard let table: Table = getTable(at: slide, in: machHeader) else {
23 | continue
24 | }
25 |
26 | if replace(at: slide, in: table) {
27 | return true
28 | }
29 | }
30 |
31 | return false
32 | }
33 |
34 | private func getTable(
35 | at slide: Int,
36 | in header: UnsafePointer
37 | ) -> Table? {
38 | var segCmd: UnsafeMutablePointer
39 | var ledCmd: UnsafeMutablePointer?
40 | var symCmd: UnsafeMutablePointer?
41 | var dynCmd: UnsafeMutablePointer?
42 | var dataSegs: [UnsafeMutableRawPointer] = []
43 |
44 | guard var lc: UnsafeMutableRawPointer = .init(bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size)) else {
45 | return nil
46 | }
47 |
48 | for _ in 0 ..< header.pointee.ncmds {
49 | segCmd = lc.assumingMemoryBound(to: segment_command_64.self)
50 |
51 | switch segCmd.pointee.cmd {
52 | case UInt32(LC_SEGMENT_64):
53 | if strcmp(&segCmd.pointee.segname, SEG_LINKEDIT) == 0 {
54 | ledCmd = segCmd
55 | } else {
56 | let seg: UnsafeMutablePointer = lc.assumingMemoryBound(to: segment_command_64.self)
57 | let nameOff: Int = MemoryLayout.size(ofValue: seg.pointee.cmd) + MemoryLayout.size(ofValue: seg.pointee.cmdsize)
58 | let name: String = .init(cString: UnsafeRawPointer(lc).advanced(by: nameOff).assumingMemoryBound(to: Int8.self))
59 |
60 | if name == SEG_DATA || name == "__DATA_CONST" || name == "__AUTH_CONST" {
61 | dataSegs.append(lc)
62 | }
63 | }
64 | case UInt32(LC_SYMTAB):
65 | symCmd = .init(OpaquePointer(segCmd))
66 | case UInt32(LC_DYSYMTAB):
67 | dynCmd = .init(OpaquePointer(segCmd))
68 | default:
69 | break
70 | }
71 |
72 | lc += Int(segCmd.pointee.cmdsize)
73 | }
74 |
75 | guard let ledCmd,
76 | let symCmd,
77 | let dynCmd
78 | else {
79 | return nil
80 | }
81 |
82 | let ledBase: Int = slide + Int(ledCmd.pointee.vmaddr - ledCmd.pointee.fileoff)
83 |
84 | guard let sym: UnsafeMutablePointer = .init(bitPattern: ledBase + Int(symCmd.pointee.symoff)),
85 | let str: UnsafeMutablePointer = .init(bitPattern: ledBase + Int(symCmd.pointee.stroff)),
86 | let ind: UnsafeMutablePointer = .init(bitPattern: ledBase + Int(dynCmd.pointee.indirectsymoff))
87 | else {
88 | return nil
89 | }
90 |
91 | for seg in dataSegs {
92 | for i: UInt32 in 0 ..< (seg.assumingMemoryBound(to: segment_command_64.self)).pointee.nsects {
93 | let sect: UnsafeMutablePointer = UnsafeMutableRawPointer(seg + MemoryLayout.size)
94 | .advanced(by: Int(i))
95 | .assumingMemoryBound(to: section_64.self)
96 |
97 | if Int32(sect.pointee.flags) & SECTION_TYPE != S_LAZY_SYMBOL_POINTERS,
98 | Int32(sect.pointee.flags) & SECTION_TYPE != S_NON_LAZY_SYMBOL_POINTERS
99 | {
100 | continue
101 | }
102 |
103 | return Table(sym: sym, str: str, ind: ind, sect: sect)
104 | }
105 | }
106 |
107 | return nil
108 | }
109 |
110 | private func replace(
111 | at slide: Int,
112 | in table: Table
113 | ) -> Bool {
114 | let indices: UnsafeMutablePointer = table.ind.advanced(by: Int(table.sect.pointee.reserved1))
115 |
116 | guard let bindings: UnsafeMutablePointer = .init(bitPattern: slide + Int(table.sect.pointee.addr)) else {
117 | return false
118 | }
119 |
120 | guard vm_protect(
121 | mach_task_self_,
122 | vm_address_t(slide + Int(table.sect.pointee.addr)),
123 | vm_size_t(table.sect.pointee.size),
124 | 0,
125 | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY
126 | ) == KERN_SUCCESS else {
127 | return false
128 | }
129 |
130 | for i: Int in 0 ..< Int(table.sect.pointee.size) / MemoryLayout.size {
131 | let inSym: UnsafeMutablePointer = indices.advanced(by: i)
132 |
133 | guard inSym.pointee != UInt32(INDIRECT_SYMBOL_ABS),
134 | inSym.pointee != INDIRECT_SYMBOL_LOCAL,
135 | inSym.pointee != INDIRECT_SYMBOL_LOCAL | UInt32(INDIRECT_SYMBOL_ABS)
136 | else {
137 | continue
138 | }
139 |
140 | let strTabOff: UInt32 = table.sym.advanced(by: Int(inSym.pointee)).pointee.n_un.n_strx
141 | let symName: String = .init(cString: table.str.advanced(by: Int(strTabOff) + 1))
142 |
143 | if symName == hook.name {
144 | hook.orig.initialize(to: bindings.advanced(by: i).pointee)
145 | bindings.advanced(by: i).initialize(to: hook.replace)
146 | return true
147 | }
148 | }
149 |
150 | return false
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Sources/Jinx/Core/Replace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Replace.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 22/03/2023.
6 | //
7 |
8 | import ObjectiveC
9 |
10 | struct Replace {
11 | static func message(
12 | _ cls: AnyClass,
13 | _ sel: Selector,
14 | with replace: OpaquePointer,
15 | orig: inout OpaquePointer?
16 | ) -> Bool {
17 | if class_isMetaClass(cls) {
18 | return classMethod(cls, sel, with: replace, orig: &orig)
19 | }
20 |
21 | guard let method: Method = class_getInstanceMethod(cls, sel),
22 | let types: UnsafePointer = method_getTypeEncoding(method)
23 | else {
24 | return false
25 | }
26 |
27 | lock.locked {
28 | orig = class_replaceMethod(cls, sel, replace, types)
29 | }
30 |
31 | var superclass: AnyClass? = class_getSuperclass(cls)
32 |
33 | while orig == nil,
34 | let thisSuper: AnyClass = superclass
35 | {
36 | if let method: Method = class_getInstanceMethod(thisSuper, sel) {
37 | lock.locked {
38 | orig = method_getImplementation(method)
39 | }
40 | }
41 |
42 | superclass = class_getSuperclass(thisSuper)
43 | }
44 |
45 | return orig != nil
46 | }
47 |
48 | private static let lock: Lock = .init()
49 |
50 | private static func classMethod(
51 | _ cls: AnyClass,
52 | _ sel: Selector,
53 | with replace: OpaquePointer,
54 | orig: inout OpaquePointer?
55 | ) -> Bool {
56 | let newSel: Selector = sel_registerName("Jinx_" + String(cString: sel_getName(sel)))
57 |
58 | guard let origMethod: Method = class_getClassMethod(cls, sel),
59 | let method: Method = class_getClassMethod(cls, sel),
60 | let types: UnsafePointer = method_getTypeEncoding(method),
61 | class_addMethod(cls, newSel, replace, types),
62 | let newMethod: Method = class_getClassMethod(cls, newSel)
63 | else {
64 | return false
65 | }
66 |
67 | lock.locked {
68 | orig = method_getImplementation(origMethod)
69 | method_exchangeImplementations(origMethod, newMethod)
70 | }
71 |
72 | return true
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Sources/Jinx/Core/Substrate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Substrate.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 08/05/2023.
6 | //
7 |
8 | struct Substrate {
9 | let hook: RebindHook
10 |
11 | func hookFunc() -> Bool {
12 | guard let MSFindSymbol: T0 = Storage.getSymbol("MSFindSymbol", in: Self.substratePath),
13 | let MSHookFunction: T1 = Storage.getSymbol("MSHookFunction", in: Self.substratePath),
14 | let symbol: UnsafeMutableRawPointer = MSFindSymbol(nil, "_" + hook.name)
15 | else {
16 | return false
17 | }
18 |
19 | var orig: UnsafeMutableRawPointer? = nil
20 |
21 | MSHookFunction(symbol, hook.replace, &orig)
22 | hook.orig.initialize(to: orig)
23 |
24 | return true
25 | }
26 |
27 | private typealias T0 = @convention(c) (OpaquePointer?, UnsafePointer) -> UnsafeMutableRawPointer?
28 | private typealias T1 = @convention(c) (UnsafeMutableRawPointer, UnsafeRawPointer, UnsafeMutablePointer) -> Void
29 |
30 | private static let substratePath: String = "/usr/lib/libsubstrate.dylib".withRootPath()
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Jinx/Extensions/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 13/04/2023.
6 | //
7 |
8 | import Darwin.POSIX
9 |
10 | public extension String {
11 | func withRootPath() -> String {
12 | #if JINX_ROOTLESS
13 | ("/var/jb" + self).resolvingSymlinks()
14 | #else
15 | self
16 | #endif
17 | }
18 |
19 | func resolvingSymlinks() -> String {
20 | var buffer: [Int8] = .init(repeating: 0, count: Int(PATH_MAX))
21 | var path: String = self
22 |
23 | if readlink(path, &buffer, buffer.count) != -1 {
24 | path = String(cString: buffer)
25 | }
26 |
27 | return path
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Jinx/Helpers/Ivar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ivar.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 21/03/2023.
6 | //
7 |
8 | import ObjectiveC
9 |
10 | public struct Ivar {
11 | @inlinable
12 | public static func get(
13 | _ name: String,
14 | for obj: AnyObject
15 | ) -> T? {
16 | if let ivar: OpaquePointer = class_getInstanceVariable(type(of: obj), name) {
17 | return Unmanaged.passUnretained(obj)
18 | .toOpaque()
19 | .advanced(by: ivar_getOffset(ivar))
20 | .assumingMemoryBound(to: T.self)
21 | .pointee
22 | }
23 |
24 | return nil
25 | }
26 |
27 | @inlinable
28 | public static func set(
29 | _ name: String,
30 | for obj: AnyObject,
31 | to val: T
32 | ) {
33 | if let ivar: OpaquePointer = class_getInstanceVariable(type(of: obj), name) {
34 | Unmanaged.passUnretained(obj)
35 | .toOpaque()
36 | .advanced(by: ivar_getOffset(ivar))
37 | .assumingMemoryBound(to: T.self)
38 | .pointee = val
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Jinx/Helpers/JinxPreferences.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JinxPreferences.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 13/04/2023.
6 | //
7 |
8 | import CoreFoundation
9 |
10 | public struct JinxPreferences {
11 | public init(
12 | for domain: String
13 | ) {
14 | let cfDomain: CFString = getCFString(from: domain)
15 |
16 | if isSandboxed() {
17 | dict = readPlist(for: "/var/mobile/Library/Preferences/\(domain).plist".withRootPath()) ?? [:]
18 | } else {
19 | let keyList: CFArray = CFPreferencesCopyKeyList(
20 | cfDomain,
21 | kCFPreferencesCurrentUser,
22 | kCFPreferencesAnyHost
23 | ) ?? CFArrayCreate(nil, nil, 0, nil)
24 |
25 | let cfDict: CFDictionary = CFPreferencesCopyMultiple(
26 | keyList,
27 | cfDomain,
28 | kCFPreferencesCurrentUser,
29 | kCFPreferencesAnyHost
30 | )
31 |
32 | dict = getDictionary(from: cfDict)
33 | }
34 | }
35 |
36 | public func get(
37 | for key: String,
38 | default val: T
39 | ) -> T {
40 | dict[key] as? T ?? val
41 | }
42 |
43 | private let dict: [String: Any]
44 | }
45 |
46 | private func readPlist(
47 | for path: String
48 | ) -> [String: Any]? {
49 | guard let url: CFURL = CFURLCreateWithFileSystemPath(nil, getCFString(from: path), .cfurlposixPathStyle, false) else {
50 | return nil
51 | }
52 |
53 | let stream: CFReadStream = CFReadStreamCreateWithFile(nil, url)
54 | var buffer: [UInt8] = .init(repeating: 0, count: 0x1000)
55 | let data: CFMutableData = CFDataCreateMutable(nil, 0)
56 |
57 | guard CFReadStreamOpen(stream) else {
58 | return nil
59 | }
60 |
61 | defer { CFReadStreamClose(stream) }
62 |
63 | while CFReadStreamHasBytesAvailable(stream) {
64 | let byteCount = CFReadStreamRead(stream, &buffer, buffer.count)
65 |
66 | if byteCount > 0 {
67 | CFDataAppendBytes(data, buffer, byteCount)
68 | }
69 | }
70 |
71 | return CFPropertyListCreateWithData(nil, data, 0, nil, nil)?.takeRetainedValue() as? [String: Any]
72 | }
73 |
74 | private func isSandboxed() -> Bool {
75 | #if os(macOS)
76 | return true
77 | #else
78 | guard let url: CFURL = CFCopyHomeDirectoryURL(),
79 | let str: CFString = CFURLGetString(url)
80 | else {
81 | return false
82 | }
83 |
84 | return CFStringCompare(str, getCFString(from: "file:///var/mobile/"), .compareBackwards) != .compareEqualTo
85 | #endif
86 | }
87 |
88 | private func getCFString(
89 | from str: String
90 | ) -> CFString {
91 | let cString: UnsafeMutablePointer = strdup(str)
92 | return CFStringCreateWithCString(nil, cString, CFStringBuiltInEncodings.UTF8.rawValue)
93 | }
94 |
95 | private func getDictionary(
96 | from cfDict: CFDictionary
97 | ) -> [String: Any] {
98 | var format: CFPropertyListFormat = .binaryFormat_v1_0
99 | let cfData: CFData? = CFPropertyListCreateData(nil, cfDict, .binaryFormat_v1_0, 0, nil)?.takeUnretainedValue()
100 | let cfPlist: CFPropertyList? = CFPropertyListCreateWithData(nil, cfData, 0, &format, nil).takeUnretainedValue()
101 |
102 | return cfPlist as? [String: Any] ?? [:]
103 | }
104 |
--------------------------------------------------------------------------------
/Sources/Jinx/Helpers/Lock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Lock.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 22/03/2023.
6 | //
7 |
8 | import Darwin.C
9 |
10 | struct Lock {
11 | private let lock: UnsafeMutablePointer
12 |
13 | init() {
14 | let _lock: UnsafeMutablePointer = .allocate(capacity: 1)
15 | _lock.initialize(to: os_unfair_lock())
16 |
17 | lock = _lock
18 | }
19 |
20 | @inlinable
21 | func locked(
22 | _ fn: () throws -> T
23 | ) rethrows -> T {
24 | os_unfair_lock_lock(lock)
25 | defer { os_unfair_lock_unlock(lock) }
26 | return try fn()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Jinx/Helpers/Storage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Storage.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 22/03/2023.
6 | //
7 |
8 | import Darwin.C
9 |
10 | struct Storage {
11 | @inlinable
12 | static func getOrigOpaque(for id: Int) -> OpaquePointer? {
13 | origsOpaque[id] ?? nil
14 | }
15 |
16 | @inlinable
17 | static func getOrigRaw(for id: Int) -> UnsafeMutableRawPointer? {
18 | origsRaw[id] ?? nil
19 | }
20 |
21 | static func getSymbol(
22 | _ name: String,
23 | in image: String
24 | ) -> T? {
25 | if let sym: T = symbols[name] as? T {
26 | return sym
27 | }
28 |
29 | let imgPtr: UnsafeMutableRawPointer = dlopen(image, RTLD_LAZY)
30 | let symPtr: UnsafeMutableRawPointer = dlsym(imgPtr, name)
31 | let ret: T? = unsafeBitCast(symPtr, to: T?.self)
32 |
33 | symbols[name] = ret
34 | dlclose(imgPtr)
35 |
36 | return ret
37 | }
38 |
39 | @inlinable
40 | static func getUUID(for obj: ObjectIdentifier) -> Int {
41 | if let ret: Int = uuids[obj] {
42 | return ret
43 | }
44 |
45 | let newID: Int = uuids.count * 10
46 | uuids[obj] = newID
47 |
48 | return newID
49 | }
50 |
51 | @inlinable
52 | static func setOrigOpaque(
53 | _ ptr: OpaquePointer?,
54 | for id: Int
55 | ) {
56 | lock.locked {
57 | origsOpaque[id] = ptr
58 | }
59 | }
60 |
61 | @inlinable
62 | static func setOrigRaw(
63 | _ ptr: UnsafeMutableRawPointer?,
64 | for id: Int
65 | ) {
66 | lock.locked {
67 | origsRaw[id] = ptr
68 | }
69 | }
70 |
71 | private static var origsOpaque: [Int: OpaquePointer?] = [:]
72 | private static var origsRaw: [Int: UnsafeMutableRawPointer?] = [:]
73 | private static var symbols: [String: Any] = [:]
74 | private static var uuids: [ObjectIdentifier: Int] = [:]
75 | private static let lock: Lock = .init()
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/Jinx/Protocols/Hook.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Hook.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 25/03/2023.
6 | //
7 |
8 | import ObjectiveC
9 |
10 | public protocol Hook {
11 | associatedtype T
12 |
13 | var cls: AnyClass? { get }
14 | var sel: Selector { get }
15 | var replace: T { get }
16 | }
17 |
18 | public extension Hook {
19 | private static var uuid: Int {
20 | Storage.getUUID(for: ObjectIdentifier(Self.self))
21 | }
22 |
23 | private static var _orig: OpaquePointer? {
24 | get { Storage.getOrigOpaque(for: Self.uuid) }
25 | set { Storage.setOrigOpaque(newValue, for: Self.uuid) }
26 | }
27 |
28 | private static func opaquePointer(
29 | from closure: U
30 | ) -> OpaquePointer {
31 | withUnsafePointer(to: closure) { UnsafeMutableRawPointer(mutating: $0).assumingMemoryBound(to: OpaquePointer.self).pointee }
32 | }
33 |
34 | private static func safeBitCast(
35 | _ ptr: OpaquePointer?,
36 | to type: U.Type
37 | ) -> U {
38 | let tPtr: UnsafePointer = withUnsafePointer(to: ptr, { UnsafeRawPointer($0).bindMemory(to: U.self, capacity: 1) })
39 | return tPtr.pointee
40 | }
41 |
42 | static var orig: T {
43 | safeBitCast(_orig, to: T.self)
44 | }
45 |
46 | @discardableResult
47 | func hook() -> Bool {
48 | guard let cls else {
49 | return false
50 | }
51 |
52 | return Replace.message(cls, sel, with: Self.opaquePointer(from: replace), orig: &Self._orig)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Jinx/Protocols/HookFunc.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HookFunc.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 25/03/2023.
6 | //
7 |
8 | public protocol HookFunc {
9 | associatedtype T
10 |
11 | var name: String { get }
12 | var replace: T { get }
13 | }
14 |
15 | public extension HookFunc {
16 | private static var uuid: Int {
17 | Storage.getUUID(for: ObjectIdentifier(Self.self))
18 | }
19 |
20 | private static var _orig: UnsafeMutableRawPointer? {
21 | get { Storage.getOrigRaw(for: Self.uuid) }
22 | set { Storage.setOrigRaw(newValue, for: Self.uuid) }
23 | }
24 |
25 | static var orig: T {
26 | return unsafeBitCast(_orig, to: T.self)
27 | }
28 |
29 | @discardableResult
30 | func hook() -> Bool {
31 | var ret: Bool = true
32 | let replacePtr: UnsafeMutableRawPointer = unsafeBitCast(replace, to: UnsafeMutableRawPointer.self)
33 |
34 | withUnsafeMutablePointer(to: &Self._orig) { origPtr in
35 | let hook: RebindHook = .init(name: name, replace: replacePtr, orig: origPtr)
36 |
37 | if Rebind(hook: hook).rebind() {
38 | ret = true
39 | } else {
40 | ret = Substrate(hook: hook).hookFunc()
41 | }
42 | }
43 |
44 | return ret
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Jinx/Protocols/HookGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HookGroup.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 25/03/2023.
6 | //
7 |
8 | import ObjectiveC
9 |
10 | public protocol HookGroup {
11 | associatedtype T0
12 | associatedtype T1 = Void
13 | associatedtype T2 = Void
14 | associatedtype T3 = Void
15 | associatedtype T4 = Void
16 | associatedtype T5 = Void
17 | associatedtype T6 = Void
18 | associatedtype T7 = Void
19 | associatedtype T8 = Void
20 | associatedtype T9 = Void
21 |
22 | var cls: AnyClass? { get }
23 | var sel0: Selector { get }
24 | var replace0: T0 { get }
25 | }
26 |
27 | public extension HookGroup {
28 | private static var uuid: Int {
29 | Storage.getUUID(for: ObjectIdentifier(Self.self))
30 | }
31 |
32 | private static func opaquePointer(
33 | from closure: U
34 | ) -> OpaquePointer {
35 | withUnsafePointer(to: closure) { UnsafeMutableRawPointer(mutating: $0).assumingMemoryBound(to: OpaquePointer.self).pointee }
36 | }
37 |
38 | private static func safeBitCast(
39 | _ ptr: OpaquePointer?,
40 | to type: U.Type
41 | ) -> U {
42 | let tPtr: UnsafePointer = withUnsafePointer(to: ptr, { UnsafeRawPointer($0).bindMemory(to: U.self, capacity: 1) })
43 | return tPtr.pointee
44 | }
45 |
46 | private static var _orig0: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 0) }
47 | private static var _orig1: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 1) }
48 | private static var _orig2: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 2) }
49 | private static var _orig3: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 3) }
50 | private static var _orig4: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 4) }
51 | private static var _orig5: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 5) }
52 | private static var _orig6: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 6) }
53 | private static var _orig7: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 7) }
54 | private static var _orig8: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 8) }
55 | private static var _orig9: OpaquePointer? { Storage.getOrigOpaque(for: Self.uuid + 9) }
56 |
57 | static var orig0: T0 { safeBitCast(_orig0, to: T0.self) }
58 | static var orig1: T1 { safeBitCast(_orig1, to: T1.self) }
59 | static var orig2: T2 { safeBitCast(_orig2, to: T2.self) }
60 | static var orig3: T3 { safeBitCast(_orig3, to: T3.self) }
61 | static var orig4: T4 { safeBitCast(_orig4, to: T4.self) }
62 | static var orig5: T5 { safeBitCast(_orig5, to: T5.self) }
63 | static var orig6: T6 { safeBitCast(_orig6, to: T6.self) }
64 | static var orig7: T7 { safeBitCast(_orig7, to: T7.self) }
65 | static var orig8: T8 { safeBitCast(_orig8, to: T8.self) }
66 | static var orig9: T9 { safeBitCast(_orig9, to: T9.self) }
67 |
68 | @discardableResult
69 | func hook() -> [Bool] {
70 | var results: [Bool] = .init(repeating: false, count: 10)
71 | var selectors: [Selector?] = [sel0] + .init(repeating: nil, count: 9)
72 | var replaces: [Any?] = [replace0] + .init(repeating: nil, count: 9)
73 |
74 | guard let cls else {
75 | return results
76 | }
77 |
78 | let mirror: Mirror = .init(reflecting: self)
79 |
80 | for child: Mirror.Child in mirror.children {
81 | if child.label?.count == 4,
82 | child.label?.hasPrefix("sel") == true,
83 | let i: Int = .init(String(child.label?.last ?? "🥺"))
84 | {
85 | selectors[i] = child.value as? Selector
86 | }
87 |
88 | if child.label?.count == 8,
89 | child.label?.hasPrefix("replace") == true,
90 | let i: Int = .init(String(child.label?.last ?? "🥺"))
91 | {
92 | replaces[i] = child.value
93 | }
94 | }
95 |
96 | for i: Int in 0 ..< 10 {
97 | if let replace: Any = replaces[i], let selector: Selector = selectors[i] {
98 | var orig: OpaquePointer? = nil
99 | results[i] = Replace.message(cls, selector, with: Self.opaquePointer(from: replace), orig: &orig)
100 | Storage.setOrigOpaque(orig, for: Self.uuid + i)
101 | }
102 | }
103 |
104 | return results
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/Jinx/Types/RebindHook.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RebindHook.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 21/03/2023.
6 | //
7 |
8 | struct RebindHook {
9 | let name: String
10 | let replace: UnsafeMutableRawPointer
11 | let orig: UnsafeMutablePointer
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Jinx/Types/Table.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Table.swift
3 | // Jinx
4 | //
5 | // Created by Lilliana on 21/03/2023.
6 | //
7 |
8 | import MachO
9 |
10 | struct Table {
11 | let sym: UnsafeMutablePointer
12 | let str: UnsafeMutablePointer
13 | let ind: UnsafeMutablePointer
14 | let sect: UnsafeMutablePointer
15 | }
16 |
--------------------------------------------------------------------------------