#!/usr/bin/env python3 import sys, os, subprocess, re, getpass, datetime # Data library = False iossupport_system = False framework = False private_framework = False # Constants library_prefix = "/usr/lib/" iossupport_system_prefix = "/System/iOSSupport" framework_prefix = "/System/Library/Frameworks/" private_framework_prefix = "/System/Library/PrivateFrameworks/" ####################################################### ##### SET THIS TO WHERE YOUR class-dump BINARY IS ##### ####################################################### username = getpass.getuser() class_dump = "/Users/" + username + "/bin/class-dump" copyright_template = """/* This file is part of Darling. Copyright (C) {} Darling Developers Darling is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Darling is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Darling. If not, see . */ """ copyright = copyright_template.format(datetime.datetime.now(datetime.timezone.utc).year) c_func_impl_stub = """ void* %s(void) { if (verbose) puts("STUB: %s called"); return NULL; } """ msg_handling = """- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [NSMethodSignature signatureWithObjCTypes: \"v@:\"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@\"Stub called: %@ in %@\", NSStringFromSelector([anInvocation selector]), [self class]); } """ # Utility functions def usage(): print("Usage: " + sys.argv[0] + " ") exit(1) def extract_library_name(name): name = re.search("/lib([A-Za-z0-9]+)\.dylib$", name).group(1) print(name) return name def extract_framework_name(name): return name[name.rfind("/") + 1:] def write_objc_source_file_locs(cmake_file, classes, num_spaces): for cls in classes: cmake_file.write(" " * num_spaces + "src/%s.m\n" % cls) # Main program start if len(sys.argv) != 3: usage() full_path = sys.argv[1] output_dir = sys.argv[2] validate_path = full_path try: os.makedirs(output_dir) except FileExistsError: pass if full_path.startswith(iossupport_system_prefix): iossupport_system = True validate_path = full_path[len(iossupport_system_prefix):] if validate_path.endswith(".dylib"): library = True target_name = extract_library_name(validate_path) elif len(validate_path) > len(framework_prefix) and validate_path[:len(framework_prefix)] == framework_prefix: framework = True target_name = extract_framework_name(validate_path) elif len(validate_path) > len(private_framework_prefix) and validate_path[ :len(private_framework_prefix)] == private_framework_prefix: private_framework = True target_name = extract_framework_name(validate_path) else: print("Failed to recognize Mach-O location") target_name = None exit(1) header_dir = output_dir + "/include/" + target_name + "/" source_dir = output_dir + "/src/" try: os.makedirs(header_dir) except FileExistsError: pass try: os.makedirs(source_dir) except FileExistsError: pass # Get C functions c_func_out = subprocess.check_output(["nm", "-Ug", full_path]) c_func_out = c_func_out.decode('utf8').strip() functions = [] for line in c_func_out.splitlines(): if line == "": continue print(line) if len(line.split(" ")) == 3: address, id, name = line.split(" ") # Remove the underscore name = name[1:] if id == "T": functions.append(name) else: print("Skipping c function output line") class_dump_output = subprocess.check_output([class_dump, full_path]).decode('utf8').strip() uses_objc = "This file does not contain any Objective-C runtime information." not in class_dump_output c_header = open(header_dir + target_name + ".h", "w") c_source = open(source_dir + target_name + ".m", "w") if uses_objc else open(source_dir + target_name + ".c", "w") c_header.write(copyright) c_source.write(copyright) c_source.write(""" #include <%s/%s.h> #include #include static int verbose = 0; __attribute__((constructor)) static void initme(void) { verbose = getenv("STUB_VERBOSE") != NULL; } """ % (target_name, target_name)) cmake = open(output_dir + "/CMakeLists.txt", "w") cmake.write("project(%s)\n\n" % target_name) # Get current and compat versions otool_out = subprocess.check_output(["otool", "-L", full_path]) otool_out = otool_out.decode('utf8').strip() version_line = otool_out.splitlines()[1] get_versions = re.compile("\\(compatibility version (.*?), current version (.*?)\\)") compat, current = get_versions.search(version_line).groups() if library: cmake.write("set(DYLIB_INSTALL_NAME \"%s\")\n" % full_path) cmake.write("set(DYLIB_COMPAT_VERSION \"%s\")\n" % compat) cmake.write("set(DYLIB_CURRENT_VERSION \"%s\")\n" % current) cmake.write("set(FRAMEWORK_VERSION \"A\")\n\n") c_header.write("\n#ifndef _%s_H_\n#define _%s_H_\n\n" % (target_name, target_name)) if uses_objc: c_header.write("#import \n\n") # typedef_void_regex = re.compile("(typedef void.+?;)", re.DOTALL | re.MULTILINE) # for typedef_void_def in typedef_void_regex.finditer(class_dump_output): # c_header.write(typedef_void_def.groups()[0]) # c_header.write("\n\n") # # blacklisted_structs = ["_NSRange"] # structs_regex = re.compile("(struct (.+?) {.+?};)", re.DOTALL | re.MULTILINE) # for struct_def in structs_regex.finditer(class_dump_output): # struct_contents, struct_name = struct_def.groups() # if struct_name in blacklisted_structs: # continue # c_header.write(struct_contents) # c_header.write("\n\n") # # typedef_struct_regex = re.compile("(typedef struct {.+?}.+?;)", re.DOTALL | re.MULTILINE) # for typedef_struct_def in typedef_struct_regex.finditer(class_dump_output): # c_header.write(typedef_struct_def.groups()[0]) # c_header.write("\n\n") classes = [] protocols_regex = re.compile("(@protocol (.+?)[\n ].+?@end)", re.DOTALL | re.MULTILINE) classes_regex = re.compile("(@interface (.+?) :.+?@end)", re.DOTALL | re.MULTILINE) blacklisted_protocols = ["NSObject", "NSSecureCoding", "NSCoding", "NSCopying", "NSFastEnumeration", "NSMutableCopying", "NSURLConnectionDataDelegate", "NSURLSessionTaskDelegate", "NSURLConnectionDelegate", "NSLocking" ] for protocol_def in protocols_regex.finditer(class_dump_output): protocol_contents, protocol_name = protocol_def.groups() if protocol_name in blacklisted_protocols: continue proto_header = open(header_dir + protocol_name + ".h", "w") proto_header.write(copyright) proto_header.write("#include \n\n") # proto_header.write(protocol_contents) proto_header.write("@protocol %s\n\n@end\n" % protocol_name) proto_header.close() c_header.write("#import <%s/%s.h>\n" % (target_name, protocol_name)) for class_def in classes_regex.finditer(class_dump_output): class_contents, class_name = class_def.groups() classes.append(class_name) class_header = open(header_dir + class_name + ".h", "w") class_header.write(copyright) class_header.write("#include \n\n") # class_header.write(class_contents) class_header.write("@interface %s : NSObject\n\n@end\n" % class_name) class_header.close() c_header.write("#import <%s/%s.h>\n" % (target_name, class_name)) class_impl = open(source_dir + class_name + ".m", "w") class_impl.write(copyright) class_impl.write("#import <%s/%s.h>\n\n" % (target_name, class_name)) class_impl.write("@implementation " + class_name + "\n\n") class_impl.write(msg_handling) class_impl.write("@end\n") c_header.write("\n") for funcname in functions: c_header.write("void* %s(void);\n" % funcname) c_source.write(c_func_impl_stub % (funcname, funcname)) if library: cmake.write("add_darling_library(%s SHARED\n" % target_name) cmake.write(" src/%s." % target_name + ("m" if uses_objc else "c") + "\n") if uses_objc: write_objc_source_file_locs(cmake, classes, 4) cmake.write(")\n") cmake.write("make_fat(%s)\n" % target_name) libraries = "system objc Foundation" if uses_objc else "system" cmake.write("target_link_libraries(%s %s)\n" % (target_name, libraries)) cmake.write("install(TARGETS %s DESTINATION libexec/darling/usr/lib)\n" % target_name) else: cmake.write("remove_sdk_framework(%s\n" % target_name) if private_framework: cmake.write(" PRIVATE\n") cmake.write(")\n\n") cmake.write("generate_sdk_framework(%s\n" % target_name) cmake.write(" VERSION ${FRAMEWORK_VERSION}\n") cmake.write(" HEADER \"include/%s\"\n" % target_name) if private_framework: cmake.write(" PRIVATE\n") cmake.write(")\n\n") cmake.write("add_framework(%s\n" % target_name) cmake.write(" FAT\n CURRENT_VERSION\n") if iossupport_system: cmake.write(" IOSSUPPORT\n") if private_framework: cmake.write(" PRIVATE\n") cmake.write(" VERSION ${FRAMEWORK_VERSION}\n\n") cmake.write(" SOURCES\n") cmake.write(" src/%s." % target_name + ("m" if uses_objc else "c") + "\n") if uses_objc: write_objc_source_file_locs(cmake, classes, 8) cmake.write("\n") cmake.write(" DEPENDENCIES\n") cmake.write(" system\n") if uses_objc: cmake.write(" objc\n") cmake.write(" Foundation\n") cmake.write(")\n") c_header.write("\n#endif\n")