├── .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 | --------------------------------------------------------------------------------