Browse Source

got it working on 10.2.2 with jailbreakd style jb and with alpha rudimentary custom settings loading

Kevin Bradley 5 years ago
parent
commit
908f9c3634

+ 2 - 0
DalesDeadBug.plist

@@ -5,9 +5,11 @@
 			"com.hulu.plus",
 			"com.netflix.Netflix",
 			"com.att.tv",
+			"com.apple.TVSettingKit",
 		);
 		Executables = (
 			TVAppStore,
+			TVSettings,
 			StoreServices,
 			installd,
 			itunesstored,

+ 42 - 7
DalesDeadBug.xm

@@ -4,12 +4,12 @@
 #import <substrate.h>
 #import <sys/utsname.h>
 #import <UIKit/UIKit.h>
-
+#import "TVSettingsTweakViewController.h"
 extern const char *__progname;
 
 #define NSLog(...)
 
-#define PLIST_PATH_Settings "/var/mobile/Library/Preferences/com.julioverne.lowerinstall.plist"
+#define PLIST_PATH_Settings "/var/mobile/Library/Preferences/com.nito.dalesdeadbug.plist"
 
 
 static BOOL Enabled;
@@ -25,12 +25,18 @@ static __strong NSString* kFormatHeader = @"/%@ ";
 - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
 {
 	%log;
+	//TODO: absolutely do NOT check this here every time... will be okay for now.
+	NSDictionary *LowerInstallPrefs = [[[NSDictionary alloc] initWithContentsOfFile:@PLIST_PATH_Settings]?:[NSDictionary dictionary] copy];
+	//HBLogInfo(@"###LowerInstallPrefs: %@", LowerInstallPrefs);
+	Enabled = (BOOL)[[LowerInstallPrefs objectForKey:@"Enabled"]?:@YES boolValue];
 	if(Enabled && field && value && kUserAgent && [field isEqualToString:kUserAgent] && kCurrentiOSVersion && kCurrentiOSVersionSpoof) {
-		HBLogInfo(@"currentiOSVaersion: %@ value: %@", kCurrentiOSVersion, value);
+		HBLogInfo(@"currentiOSVersion: %@ value: %@", kCurrentiOSVersion, value);
 		if([value rangeOfString:kCurrentiOSVersion].location != NSNotFound) {
-			HBLogInfo(@"### are we inside here???");
+			//HBLogInfo(@"### are we inside here???");
 			value = [value stringByReplacingOccurrencesOfString:[NSString stringWithFormat:kFormatHeader, kCurrentiOSVersion] withString:[NSString stringWithFormat:kFormatHeader, [NSString stringWithUTF8String:kCurrentiOSVersionSpoof]]];
-			value = [value stringByReplacingOccurrencesOfString:[NSString stringWithFormat:kFormatHeader, kCurrentDeviceType] withString:[NSString stringWithFormat:kFormatHeader, [NSString stringWithUTF8String:kCurrentDeviceTypeSpoof]]];
+			//HBLogInfo(@"### are we inside here??? 2");
+			//value = [value stringByReplacingOccurrencesOfString:[NSString stringWithFormat:kFormatHeader, kCurrentDeviceType] withString:[NSString stringWithFormat:kFormatHeader, [NSString stringWithUTF8String:kCurrentDeviceTypeSpoof]]];
+			//HBLogInfo(@"### are we inside here??? 3");
 		} else {
 			
 			HBLogInfo(@"#### value: %@ not found in %@", value, kCurrentiOSVersion);
@@ -88,7 +94,7 @@ static __strong NSString* kFormatHeader = @"/%@ ";
 	BOOL orig = %orig;
 	return YES;
 }
-*/
+
 - (NSArray *)supportedDevices
 {
 	NSArray* ret = %orig?:@[];
@@ -99,6 +105,7 @@ static __strong NSString* kFormatHeader = @"/%@ ";
 	}
 	return ret;
 }
+*/
 %end
 %end
 
@@ -136,6 +143,32 @@ static void settingsChangedLowerInstall()
 	}
 }
 
+%group settingsHooks
+%hook TVSettingsMainViewController
+
+- (id)loadSettingGroups {
+	
+	%log;
+	NSArray* groups = %orig;
+	TSKSettingGroup *group = groups[0];
+	// +[<TSKSettingItem: 0x1ad71bc88> childPaneItemWithTitle:General description:(null) representedObject:(null) keyPath:(null) childControllerClass:TVSettingsGeneralViewController]
+	Class theClass = NSClassFromString(@"TVSettingsTweakViewController");
+	id testItem = [TSKSettingItem childPaneItemWithTitle:@"Tweaks" description:nil representedObject:nil keyPath:nil childControllerClass:theClass];
+	HBLogDebug(@"testItem = %@", testItem);
+	NSArray *settingsItems = [group valueForKey:@"settingItems"];
+	HBLogDebug(@"settingsItems = %@", settingsItems);
+	NSMutableArray *newItems = [settingsItems mutableCopy];
+	//[newItems addObject:testItem];
+	[newItems insertObject:testItem atIndex:7];
+	HBLogDebug(@"newItems = %@", newItems);
+	[group setSettingItems:newItems];
+	
+	return @[group];
+}
+
+%end
+%end
+
 %ctor
 {
 	HBLogInfo(@"### DalesDeadBug reporting for duty!");
@@ -151,9 +184,11 @@ static void settingsChangedLowerInstall()
 	if(strcmp(__progname, "itunesstored") == 0) {
 		HBLogInfo(@"### DalesDeadBug: itunesstored");
 		%init(itunesstoredHooks);
-	} else {
+	} else if(strcmp(__progname, "installd") == 0){
 		HBLogInfo(@"### DalesDeadBug: installd");
 		%init(installdHooks);
+	} else {
+		%init(settingsHooks);
 	}
 }
 

+ 6 - 4
Makefile

@@ -2,16 +2,18 @@ ARCHS = arm64
 TARGET = appletv
 export GO_EASY_ON_ME=1
 export SDKVERSION=10.2
-THEOS_DEVICE_IP=apple-tv.local
+THEOS_DEVICE_IP=192.168.0.13
 
 include theos/makefiles/common.mk
 
 TWEAK_NAME = DalesDeadBug
 
-DalesDeadBug_FILES = DalesDeadBug.xm Shim.xm
+DalesDeadBug_FILES = DalesDeadBug.xm Shim.xm TVSettingsTweakViewController.m
 DalesDeadBug_LIBRARIES = substrate
-DalesDeadBug_FRAMEWORKS = Foundation UIKit
-DalesDeadBug_LDFLAGS = -Wl,-segalign,4000
+DalesDeadBug_FRAMEWORKS = Foundation UIKit TVSettingKit
+DalesDeadBug_CFLAGS += -I.
+DalesDeadBug_LDFLAGS = -undefined dynamic_lookup
+#DalesDeadBug_LDFLAGS = -Wl,-segalign,4000
 DalesDeadBug_CODESIGN_FLAGS=-Sddb.xml
 
 export ARCHS = arm64

+ 59 - 0
TVSettingsTweakViewController.h

@@ -0,0 +1,59 @@
+//
+//  TVSettingsTweakViewController.h
+//  nitoTV4
+//
+//  Created by Kevin Bradley on 7/28/18.
+//  Copyright © 2018 nito. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface TVSettingsPreferenceFacade : NSObject
+{
+    NSString *_domain;    // 16 = 0x10
+    NSString *_containerPath;    // 24 = 0x18
+}
+
+@property(readonly, copy, nonatomic) NSString *containerPath; // @synthesize containerPath=_containerPath;
+@property(readonly, copy, nonatomic) NSString *domain; // @synthesize domain=_domain;
+
+- (id)valueForUndefinedKey:(id)arg1;    // IMP=0x0000000100011ce0
+- (void)setValue:(id)arg1 forUndefinedKey:(id)arg2;    // IMP=0x0000000100011b98
+- (id)_initWithDomain:(id)arg1 containerPath:(id)arg2 notifyChanges:(_Bool)arg3;    // IMP=0x0000000100011a44
+- (id)initWithDomain:(id)arg1 notifyChanges:(_Bool)arg2;    // IMP=0x0000000100011a30
+- (id)initWithDomain:(id)arg1 containerPath:(id)arg2;    // IMP=0x00000001000119d0
+
+@end
+
+@interface TSKSettingItem: NSObject
++(id)childPaneItemWithBundle:(id)arg1 representedObject:(id)arg2 ;
++(id)valueForSettingItem:(id)arg1 ;
++(void)setValue:(id)arg1 forSettingItem:(id)arg2 ;
++(id)actionItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 target:(id)arg5 action:(SEL)arg6 ;
++(id)childPaneItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 childControllerClass:(Class)arg5 ;
++(id)childPaneItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 childControllerBlock:(/*^block*/id)arg5 ;
++(id)childPaneItemWithBundle:(id)arg1 ;
++(id)titleItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 ;
++(id)textInputItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 ;
++(id)toggleItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 onTitle:(id)arg5 offTitle:(id)arg6 ;
++(id)multiValueItemWithTitle:(id)arg1 description:(id)arg2 representedObject:(id)arg3 keyPath:(id)arg4 availableValues:(id)arg5 ;
+@end
+
+@interface TSKSettingGroup : TSKSettingItem
+@property (nonatomic,copy) NSArray * settingItems;
++(id)groupWithTitle:(id)arg1 settingItems:(id)arg2;
+@end
+
+@interface TSKTableViewController : UITableViewController
+
+@end
+
+@interface TSKViewController: TSKTableViewController
+
+-(id)loadSettingGroups;
+@property (nonatomic,copy,readonly) NSArray * settingGroups;
+@end
+
+@interface TVSettingsTweakViewController : TSKViewController
+
+@end

+ 56 - 0
TVSettingsTweakViewController.m

@@ -0,0 +1,56 @@
+//
+//  TVSettingsTweakViewController.m
+//  nitoTV4
+//
+//  Created by Kevin Bradley on 7/28/18.
+//  Copyright © 2018 nito. All rights reserved.
+//
+
+#import "TVSettingsTweakViewController.h"
+
+@implementation TVSettingsTweakViewController
+
+
+- (id)loadSettingGroups {
+    
+    NSMutableArray *_backingArray = [NSMutableArray new];
+    Class theClass = NSClassFromString(@"DDBSettingsController");
+    TSKSettingItem *testItem = [TSKSettingItem childPaneItemWithTitle:@"Dales Dead Bug" description:@"You just be careful, Computers have already beaten the Communists at chess. Next thing you know, they'll be beating humans. - Dale Gribble" representedObject:nil keyPath:nil childControllerClass:theClass];
+    TSKSettingGroup *group = [TSKSettingGroup groupWithTitle:nil settingItems:@[testItem]];
+    [_backingArray addObject:group];
+    [self setValue:_backingArray forKey:@"_settingGroups"];
+    
+    return _backingArray;
+    
+}
+
+@end
+
+@interface DDBSettingsController: TSKViewController
+
+@end
+
+@implementation DDBSettingsController
+
+- (id)loadSettingGroups {
+    
+    /*
+     
+     +[<TSKSettingItem: 0x1ad71bc88> toggleItemWithTitle:Automatically Update Apps description:(null) representedObject:<TVSettingsPreferenceFacade: 0x1c0438480> keyPath:AutoUpdatesEnabled onTitle:(null) offTitle:(null)]
+     */
+    
+    id facade = [[ NSClassFromString(@"TVSettingsPreferenceFacade") alloc] initWithDomain:@"com.nito.dalesdeadbug" notifyChanges:TRUE];
+    
+    
+    NSMutableArray *_backingArray = [NSMutableArray new];
+    Class theClass = NSClassFromString(@"TVSettingsGeneralViewController");
+    TSKSettingItem *toggleItem = [TSKSettingItem toggleItemWithTitle:@"Spoof version" description:@"Enables you to install newer application versions that would normally not be supported on your current tvOS version." representedObject:facade keyPath:@"Enabled" onTitle:nil offTitle:nil];
+    TSKSettingGroup *group = [TSKSettingGroup groupWithTitle:nil settingItems:@[toggleItem]];
+    [_backingArray addObject:group];
+    [self setValue:_backingArray forKey:@"_settingGroups"];
+    
+    return _backingArray;
+    
+}
+
+@end

+ 21 - 0
ddb.xml

@@ -0,0 +1,21 @@
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>com.apple.private.skip-library-validation</key>
+    <true/>
+    <key>platform-application</key>
+    <true/>
+    <key>com.apple.private.security.no-container</key>
+    <true/>
+    <key>get-task-allow</key>
+    <true/>
+    <key>com.apple.system-task-ports</key>
+    <true/>
+    <key>task_for_pid-allow</key>
+    <true/>
+    <key>run-unsigned-code</key>
+    <true/>
+    <key>dynamic-codesigning</key>
+    <true/>
+</dict>
+</plist>

+ 1 - 1
layout/DEBIAN/control

@@ -2,7 +2,7 @@ Package: com.nito.dalesdeadbug
 Name: Dales Dead Bug
 Pre-Depends: cy+model.appletv
 Depends: mobilesubstrate
-Version: 0.0.1
+Version: 1.0
 Architecture: appletvos-arm64
 Description: Get unsupported Applications to install on lower version
 Maintainer: Kevin Bradley

+ 1 - 1
layout/DEBIAN/postinst

@@ -10,4 +10,4 @@ launchctl unload /System/Library/LaunchDaemons/com.apple.appstored.plist
 launchctl load /System/Library/LaunchDaemons/com.apple.appstored.plist
 
 #/usr/libexec/substrate
-killall -9 TVAppStore itunesstored appstored installd ignition Hopper Hulu Plex 'DIRECTV NOW'
+killall -9 TVAppStore itunesstored appstored installd ignition Hopper Hulu Plex 'DIRECTV NOW' TVSettings

+ 309 - 0
theos/include/substitute.h

@@ -0,0 +1,309 @@
+/*
+    libsubstitute - https://github.com/comex/substitute
+    This header file itself is in the public domain (or in any jusrisdiction
+    where the former is ineffective, CC0 1.0).
+*/
+
+#pragma once
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Error codes */
+enum {
+    /* TODO add numbers */
+    SUBSTITUTE_OK = 0,
+
+    /* substitute_hook_functions: can't patch a function because it's too short-
+     * i.e. there's an unconditional return instruction inside the patch region
+     * (and not at its end) */
+    SUBSTITUTE_ERR_FUNC_TOO_SHORT = 1,
+
+    /* substitute_hook_functions: can't patch a function because one of the
+     * instructions within the patch region is one of a few special problematic
+     * cases - if you get this on real code, the library should probably be
+     * updated to handle that case properly */
+    SUBSTITUTE_ERR_FUNC_BAD_INSN_AT_START = 2,
+
+    /* substitute_hook_functions: can't patch a function because one of the
+     * instructions within the patch region (other than the last instruction)
+     * is a call - meaning that a return address within the region (i.e. about
+     * to point to clobbered code) could be on some thread's stack, where we
+     * can't easily find and patch it.  This check is skipped if
+     * SUBSTITUTE_NO_THREAD_SAFETY is set. */
+    SUBSTITUTE_ERR_FUNC_CALLS_AT_START = 3,
+
+    /* substitute_hook_functions: can't patch a function because the (somewhat
+     * cursory) jump analysis found a jump later in the function to within the
+     * patch region at the beginning */
+    SUBSTITUTE_ERR_FUNC_JUMPS_TO_START = 4,
+
+    /* out of memory */
+    SUBSTITUTE_ERR_OOM = 5,
+
+    /* substitute_hook_functions:    mmap, mprotect, vm_copy, or
+     *                               vm_remap failure
+     * substitute_hook_objc_message: vm_remap failure
+     * Most likely to come up with substitute_hook_functions if the kernel is
+     * preventing pages from being marked executable. */
+    SUBSTITUTE_ERR_VM = 6,
+
+    /* substitute_hook_functions: not on the main thread, and you did not pass
+     * SUBSTITUTE_NO_THREAD_SAFETY */
+    SUBSTITUTE_ERR_NOT_ON_MAIN_THREAD = 7,
+
+    /* substitute_hook_functions: when trying to patch the PC of other threads
+     * (in case they were inside the patched prolog when they were suspended),
+     * found a PC that was in the patch region but seemingly not at an
+     * instruction boundary
+     * The hooks were otherwise completed, but the thread in question will
+     * probably crash now that its code has changed under it. */
+    SUBSTITUTE_ERR_UNEXPECTED_PC_ON_OTHER_THREAD = 8,
+
+    /* substitute_hook_functions: destination was out of range, and mmap
+     * wouldn't give us a trampoline in range */
+    SUBSTITUTE_ERR_OUT_OF_RANGE = 9,
+
+    /* substitute_interpose_imports: couldn't redo relocation for an import
+     * because the type was unknown */
+    SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE = 10,
+
+    /* substitute_hook_objc_message: no such selector existed in the class's
+     * inheritance tree */
+    SUBSTITUTE_ERR_NO_SUCH_SELECTOR = 11,
+
+    /* substitute_hook_functions: OS error suspending other threads */
+    SUBSTITUTE_ERR_ADJUSTING_THREADS = 12,
+
+    _SUBSTITUTE_CURRENT_MAX_ERR_PLUS_ONE,
+};
+
+/* Get a string representation for a SUBSTITUTE_* error code. */
+const char *substitute_strerror(int err);
+
+struct substitute_function_hook {
+    /* The function to hook.  (On ARM, Thumb functions are indicated as usual
+     * for function pointers.) */
+    void *function;
+    /* The replacement function. */
+    void *replacement;
+    /* Optional: out *pointer* to function pointer to call old implementation
+     * (i.e. given 'void (*old_foo)(...);', pass &old_foo) */
+    void *old_ptr;
+    /* Currently unused; pass 0.  (Protip: When using C {} struct initializer
+     * syntax, you can just omit this.) */
+    int options;
+};
+
+/* substitute_hook_functions options */
+enum {
+    SUBSTITUTE_NO_THREAD_SAFETY = 1,
+};
+
+/* Patch the machine code of the specified functions to redirect them to the
+ * specified replacements.
+ *
+ * After hooking, you can use the function pointer written to 'old_ptr' to call
+ * the original implementation.  (It points to a trampoline that executes the
+ * original first few instructions, which were written over in the real
+ * function, then jumps there for the rest.)
+ *
+ * This function must be called from the main thread.  In return, it attempts
+ * to be atomic in the face of concurrent calls to the functions being hooked.
+ * Since there is no way to do that directly, it resorts to pausing all other
+ * threads while doing its job; and since there is no way to do *that*
+ * atomically on currently supported platforms, it does so by pausing each
+ * thread one at a time.  If multiple threads each tried to pause each other
+ * this way, the process would be deadlocked, so mutual exclusion must be
+ * implicitly provided by running on the main thread.
+ *
+ * You can disable the main thread check and all synchronization by passing
+ * SUBSTITUTE_NO_THREAD_SAFETY.
+ *
+ * Why not just use a mutex to prevent deadlock?  That would work between
+ * multiple calls into libsubstitute, but there may be other libraries that
+ * want to do the same thing and would not know about our mutex.  My hope is
+ * that using the main thread is sufficiently natural that the author of any
+ * other similar library which cares about atomicity, noticing the same
+ * concern, would independently come up with the same restriction - at least,
+ * if they do not find an easier method to avoid deadlocks.  Note that all
+ * existing hooking libraries I know of make no attempt to do any
+ * synchronization at all; this is fine if hooking is only done during
+ * initialization while the process is single threaded, but I want to properly
+ * support dynamic injection.  (Note - if there is such an easier method on OS
+ * X that does not involve spawning a separate process, I'd be curious to hear
+ * about it.)
+ *
+ *
+ * @hooks    see struct substitute_function_hook
+ * @nhooks   number of hooks
+ * @recordp  if non-NULL, on success receives a pointer that can be used to
+ *           cleanly undo the hooks; currently unimplemented, so pass NULL
+ * @options  options - see above
+ * @return   SUBSTITUTE_OK, or any of most of the SUBSTITUTE_ERR_*
+ */
+struct substitute_function_hook_record;
+int substitute_hook_functions(const struct substitute_function_hook *hooks,
+                              size_t nhooks,
+                              struct substitute_function_hook_record **recordp,
+                              int options);
+
+#if 1 /* declare dynamic linker-related stuff? */
+
+#ifdef __APPLE__
+#include <mach-o/nlist.h>
+#ifdef __LP64__
+typedef struct nlist_64 substitute_sym;
+#else
+typedef struct nlist substitute_sym;
+#endif
+#else
+#error No definition for substitute_sym!
+#endif
+
+struct substitute_image {
+#ifdef __APPLE__
+    intptr_t slide;
+    void *dlhandle;
+    const void *image_header;
+#endif
+    /* possibly private fields... */
+};
+
+/* Look up an image currently loaded into the process.
+ *
+ * @filename the executable/library path (c.f. dyld(3) on Darwin)
+ * @return   a handle, or NULL if the image wasn't found
+ */
+struct substitute_image *substitute_open_image(const char *filename);
+
+/* Release a handle opened with substitute_open_image.
+ *
+ * @handle a banana
+ */
+void substitute_close_image(struct substitute_image *handle);
+
+/* Look up private symbols in an image currently loaded into the process.
+ *
+ * @handle handle opened with substitute_open_image
+ * @names  an array of symbol names to search for
+ * @syms   an array of void *, one per name; on return, each entry will be
+ *         filled in with the corresponding symbol address, or NULL if the
+ *         symbol wasn't found
+ *         (on ARM, this will be | 1 for Thumb functions)
+ * @nsyms  number of names
+ *
+ * @return SUBSTITUTE_OK (maybe errors in the future)
+ */
+int substitute_find_private_syms(struct substitute_image *handle,
+                                 const char **__restrict names,
+                                 void **__restrict syms,
+                                 size_t nsyms);
+
+/* Get a pointer corresponding to a loaded symbol table entry.
+ * @handle handle containing the symbol
+ * @sym    symbol
+ * @return the pointer - on ARM, this can be | 1 for Thumb, like everything
+ * else
+ */
+void *substitute_sym_to_ptr(struct substitute_image *handle, substitute_sym *sym);
+
+struct substitute_import_hook {
+    /* The symbol name - this is raw, so C++ symbols are mangled, and on OS X
+     * most symbols have '_' prepended. */
+    const char *name;
+    /* The new import address. */
+    void *replacement;
+    /* Optional: out pointer to old value.  if there are multiple imports for
+     * the same symbol, only one address is returned (hopefully they are all
+     * equal) */
+    void *old_ptr;
+    /* Currently unused; pass 0.  (Protip: When using C {} struct initializer
+     * syntax, you can just omit this.) */
+    int options;
+};
+
+/* Directly modify the GOT/PLT entries from a specified image corresponding to
+ * specified symbols.
+ *
+ * This can be used to 'hook' functions or even exported variables.  Compared
+ * to substitute_hook_functions, it has the following advantages:
+ *
+ * - It does not require the ability to patch executable code; accordingly, it
+ *   can (from a technical rather than policy perspective) be used in sandboxed
+ *   environments like iOS or PaX MPROTECT.
+ * - On platforms without RELRO or similar, it is thread safe, as the patches
+ *   are done using atomic instructions.
+ * - It does not require architecture specific code.
+ * - It can be used to modify a single library's view of the world without
+ *   affecting the rest of the program.
+ *
+ * ...and the following disadvantages:
+ *
+ * - It only works for exported functions, and even then will not catch calls
+ *   from a library to its own exported functions.
+ * - At present, it *only* works for a single importing library at a time.
+ *   Although it is not difficult on most platforms to iterate loaded libraries
+ *   in order to hook all of them, substitute does not currently provide this
+ *   functionality, traversing all libraries' symbol tables may be slow, and in
+ *   any case there is the matter of new importers being loaded after the fact.
+ *
+ * @handle   handle of the importing library
+ * @hooks    see struct substitute_import_hook
+ * @nhooks   number of hooks
+ * @recordp  if non-NULL, on success receives a pointer that can be used to
+ *           cleanly undo the hooks; currently unimplemented, so pass NULL
+ * @options  options - pass 0
+ * @return   SUBSTITUTE_OK
+ *           SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE
+ *           SUBSTITUTE_ERR_VM - in the future with RELRO on Linux
+ */
+struct substitute_import_hook_record;
+int substitute_interpose_imports(const struct substitute_image *handle,
+                                 const struct substitute_import_hook *hooks,
+                                 size_t nhooks,
+                                 struct substitute_import_hook_record **recordp,
+                                 int options);
+
+
+#endif /* 1 */
+
+#if defined(__APPLE__)
+#include <objc/runtime.h>
+/* Hook a method implementation for a given Objective-C class.  By itself, this
+ * function is thread safe: it is simply a wrapper for the atomic Objective-C
+ * runtime call class_replaceMethod, plus the superclass-call generation
+ * functionality described below, and some code to ensure atomicity if the
+ * method is called while the function is in progress.  However, it will race
+ * with code that modifies class methods without using atomic runtime calls,
+ * such as Substrate.
+ *
+ * @klass            the class
+ * @selector         the selector
+ * @replacement      the new implementation (other APIs would call this an
+ *                   IMP, but that isn't in general the real type of the
+ *                   implementation, so declared as a void * here)
+ * @old_ptr          optional - out pointer to the 'old implementation'.
+ *                   If there is no old implementation, a custom IMP is
+ *                   returned that delegates to the superclass.  This IMP can
+ *                   be freed if desired with substitute_free_created_imp.
+ * @created_imp_ptr  optional - out pointer to whether a fake superclass-call
+ *                   IMP has been placed in <old_ptr>
+ *
+ * @return           SUBSTITUTE_OK
+ *                   SUBSTITUTE_ERR_NO_SUCH_SELECTOR
+ */
+int substitute_hook_objc_message(Class klass, SEL selector, void *replacement,
+                                 void *old_ptr, bool *created_imp_ptr);
+
+void substitute_free_created_imp(IMP imp);
+#endif
+
+#ifdef __cplusplus
+} /* extern */
+#endif

BIN
theos/vendor/lib/libsubstitute.dylib