Something went wrong. Try again.
Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703#!/usr/bin/env python3
import osfrom typing import List, Dictimport subprocessimport reimport pathlibfrom enum import Enumimport shutilimport datetime
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
## USING THIS SCRIPT:# * edit `DYLDINFO`, `OTOOL`, and `NM` to refer to the corresponding programs to analyze x86_64 Mach-Os.# * edit `XCODE_PATH` to point to the Xcode application.# * edit `SYSTEM_ROOT` to point to the root of a macOS or stock Darling installation.# * if desired, edit `OUT_DIR` to point to where the stub frameworks will be generated; the default is the best in most cases.#
#### EDIT THESE TO USE SCRIPT###
DYLDINFO = 'x86_64-apple-darwin11-dyldinfo'OTOOL = 'x86_64-apple-darwin11-otool'NM = 'x86_64-apple-darwin11-nm'
XCODE_PATH = '/data/darling/Applications/Xcode.app'SYSTEM_ROOT = '/usr/local/libexec/darling'OUT_DIR = os.path.join(SCRIPT_DIR, '..', 'src', 'frameworks', 'dev-stubs')
#### DON'T EDIT ANYTHING ELSE###
## most of this was just trial-and-errored until i got something that worked.# in particular, the RPATH computations are iffy, but they get the job done.#
MACH_BINARY_MIME_TYPE = 'application/x-mach-binary'XCODE_MAIN_BINARY = os.path.join(XCODE_PATH, 'Contents', 'MacOS', 'Xcode')
# extra RPATHs added to the RPATHs of plugins (not the main Xcode binary) and their dependencies.EXTRA_PLUGIN_RPATHS = [ os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks'), os.path.join(XCODE_PATH, 'Contents', 'OtherFrameworks'), os.path.join(XCODE_PATH, 'Contents', 'Frameworks'), os.path.join(XCODE_PATH, 'Contents', 'PlugIns'), os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'Frameworks'), os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'usr', 'lib'), os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Library', 'Frameworks'), os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'PrivateFrameworks'),
os.path.join(XCODE_PATH, 'Contents', 'PlugIns', 'Xcode3Core.ideplugin', 'Contents', 'Frameworks'), os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks', 'XCBuild.framework', 'Versions', 'A', 'Frameworks'),]
# RPATH templates to try to add for each binary, if they exist.# `{}` is substituted with the full path to the binary.TRY_PATH_TEMPLATES = [ '{}/../Frameworks', '{}/Frameworks', '{}/../SharedFrameworks', '{}/SharedFrameworks', '{}/../OtherFrameworks', '{}/OtherFrameworks', '{}/../SystemFrameworks', '{}/SystemFrameworks', '{}/../PlugIns', '{}/PlugIns',]
# these are binaries that are repeated often throughout the source and we don't really care about them.# it's mainly just lots of copies of the Swift system libraries.EXCLUDED_BINARIES = [ 'libswiftAccelerate.dylib', 'libswiftAppKit.dylib', 'libswiftAVFoundation.dylib', 'libswiftCloudKit.dylib', 'libswiftCompression.dylib', 'libswiftContacts.dylib', 'libswiftCoreAudio.dylib', 'libswiftCoreData.dylib', 'libswiftCore.dylib', 'libswiftCoreFoundation.dylib', 'libswiftCoreGraphics.dylib', 'libswiftCoreImage.dylib', 'libswiftCoreLocation.dylib', 'libswiftCoreMedia.dylib', 'libswiftCoreMIDI.dylib', 'libswiftCryptoTokenKit.dylib', 'libswiftDarwin.dylib', 'libswift_Differentiation.dylib', 'libswiftDispatch.dylib', 'libswiftFoundation.dylib', 'libswiftGameplayKit.dylib', 'libswiftGLKit.dylib', 'libswiftIntents.dylib', 'libswiftIOKit.dylib', 'libswiftMapKit.dylib', 'libswiftMetal.dylib', 'libswiftMetalKit.dylib', 'libswiftModelIO.dylib', 'libswiftNaturalLanguage.dylib', 'libswiftNetwork.dylib', 'libswiftObjectiveC.dylib', 'libswiftOpenCL.dylib', 'libswiftos.dylib', 'libswiftPhotos.dylib', 'libswiftQuartzCore.dylib', 'libswiftRemoteMirror.dylib', 'libswiftSafariServices.dylib', 'libswiftSceneKit.dylib', 'libswiftsimd.dylib', 'libswiftSpriteKit.dylib', 'libswiftSwiftLang.dylib', 'libswiftSwiftOnoneSupport.dylib', 'libswiftVision.dylib', 'libswiftXCTest.dylib', 'libswiftXPC.dylib',]
# these are the short names for the binaries we want to generate stubs forIMPORTANT_BINARIES = [ 'AppKit', 'AudioToolbox', 'Cocoa', 'CoreData', 'CoreGraphics', 'CoreText', 'ImageIO', 'OpenGL', 'QuartzCore',]
COPYRIGHT_HEADER = """/* * 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 <http://www.gnu.org/licenses/>. */"""
STUB_HEADER = """#include <stdlib.h>#include <stdio.h>
static int verbose = 0;
__attribute__((constructor))static void initme(void) { verbose = getenv("STUB_VERBOSE") != NULL;}
void __simple_kprintf(const char* format, ...) __attribute__((format(printf, 1, 2)));
#define LOG_FUNC __simple_kprintf"""
TEMPLATE_STUB = """void* {0}(void) {{ if (verbose) LOG_FUNC("STUB: {0} called\\n"); return NULL;}};"""
TEMPLATE_INTERFACE = """@interface {} : NSObject@end"""
TEMPLATE_CATEGORY = """@interface {}@end"""
TEMPLATE_IMPL = """@implementation {}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{{ return [NSMethodSignature signatureWithObjCTypes: "v@:"];}}
- (void)forwardInvocation:(NSInvocation *)anInvocation{{ NSLog(@"Stub called: %@ in %@", NSStringFromSelector([anInvocation selector]), [self class]);}}
"""
TEMPLATE_METHOD = """{0} (id){1}{{ NSLog(@"Stub called: %@ in %@", NSStringFromSelector(_cmd), [self class]); return nil;}}"""
CMAKE_TEMPLATE = """project({0}_stub)
set(DYLIB_COMPAT_VERSION "1.0.0")set(DYLIB_CURRENT_VERSION "1.0.0")set(FRAMEWORK_VERSION "{1}")
add_framework({0} FAT CURRENT_VERSION VERSION ${{FRAMEWORK_VERSION}} TARGET_NAME {0}${{STUB_SUFFIX}} ${{NO_INSTALL_ARG}}
SOURCES src/classes.m src/main.m
DEPENDENCIES system Foundation)
#target_include_directories({0}${{STUB_SUFFIX}} BEFORE PRIVATE include)"""
# most variables are just string keys, so that's the default.# these overrides for the definitions of certain variables prevent them from being defined as strings and instead define them with the appropriate values.VAR_OVERRIDES = { 'AppKit': { 'NSUnderlineByWordMask': 'NSUInteger NSUnderlineByWordMask = 0x8000;', 'NSApp': 'NSObject *NSApp = nil;', 'NSFontWeightBold': 'const CGFloat NSFontWeightBold = 0x3fd99999a0000000;', 'NSFontWeightMedium': 'const CGFloat NSFontWeightMedium = 0x3fcd70a3e0000000;', 'NSFontWeightRegular': 'const CGFloat NSFontWeightRegular = 0x0000000000000000;', 'NSFontWeightSemibold': 'const CGFloat NSFontWeightSemibold = 0x3fd3333340000000;', 'NSFontWeightThin': 'const CGFloat NSFontWeightThin = 0xbfe3333340000000;', 'NSFontWeightLight': 'const CGFloat NSFontWeightLight = 0xbfd99999a0000000;', 'NSFontWeightUltraLight': 'const CGFloat NSFontWeightUltraLight = 0xbfe99999a0000000;', 'NSFontWeightBlack': 'const CGFloat NSFontWeightBlack = 0x3fe3d70a40000000;', 'NSFontWeightHeavy': 'const CGFloat NSFontWeightHeavy = 0x3fe1eb8520000000;', 'NSViewNoIntrinsicMetric': 'const CGFloat NSViewNoIntrinsicMetric = 0xbff0000000000000;', 'NSAppKitVersionNumber': 'const double NSAppKitVersionNumber = 1504;', }, 'CoreGraphics': { 'CGRectZero': 'const CGRect CGRectZero = {{0, 0}, {0, 0}};', 'CGRectNull': 'const CGRect CGRectNull = {{INFINITY, INFINITY}, {0, 0}};', 'CGPointZero': 'const CGPoint CGPointZero = {0, 0};', 'CGSizeZero': 'const CGSize CGSizeZero = {0, 0};', 'CGRectInfinite': 'const CGRect CGRectInfinite = {{0, 0}, {INFINITY, INFINITY}};', 'kCGDisplayPixelHeight': 'unsigned int kCGDisplayPixelHeight = 1;', 'kCGDisplayPixelWidth': 'unsigned int kCGDisplayPixelWidth = 1;', }, 'QuartzCore': { 'CATransform3DIdentity': 'const CATransform3D CATransform3DIdentity = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};' },}
# sometimes, functions are incorrectly detected as variables. this can be used to override certain symbols as functions in those cases.# note that this should rarely be necessary, since we double-check with `nm`.FUNC_OVERRIDES = { #'CoreGraphics': [ # 'CGRectGetMinX', # 'CGRectGetWidth', #],}
# this is necessary to ensure certain functions that we need but Xcode doesn't need still get stubbed.ENSURE_FUNCS = { 'CoreGraphics': [ 'CGSSetDenyWindowServerConnections', ],}
# this is necessary to ensure certain methods get stubbed as individual methods.# for objc, we can stub using an invocation forwarding method. this is fine for the vast majority of cases.# however, Xcode swizzles some methods, so they have to exist as individual methods.ENSURE_METHODS = { 'AppKit': { 'NSColorWell': [ '-activate:', '-deactivate', ], 'NSObject (BindingSupport)': [ '-bind:toObject:withKeyPath:options:', '-infoForBinding:', '-unbind:', ], 'NSObject (NSKeyValueBindingCreation)': [ '-exposedBindings', ], },}
# this is added to the top of the `main.m` source file for the corresponding framework.PROLOGUES = { 'AppKit': """#import <CoreGraphics/CGGeometry.h>""",
'CoreGraphics': """#include <math.h>
#ifdef __LP64__ typedef double CGFloat;#else typedef float CGFloat;#endif
struct CGPoint { CGFloat x; CGFloat y;};typedef struct CGPoint CGPoint;
struct CGSize { CGFloat width; CGFloat height;};typedef struct CGSize CGSize;
struct CGVector { CGFloat dx; CGFloat dy;};typedef struct CGVector CGVector;
struct CGRect { CGPoint origin; CGSize size;};typedef struct CGRect CGRect;""",
'QuartzCore': """#ifdef __LP64__ typedef double CGFloat;#else typedef float CGFloat;#endif
typedef struct { CGFloat m11, m12, m13, m14; CGFloat m21, m22, m23, m24; CGFloat m31, m32, m33, m34; CGFloat m41, m42, m43, m44;} CATransform3D;""",}
for bin in IMPORTANT_BINARIES: if not bin in VAR_OVERRIDES: VAR_OVERRIDES[bin] = {} if not bin in FUNC_OVERRIDES: FUNC_OVERRIDES[bin] = []
RPATH_REGEX = r"\s*cmd LC_RPATH\n\s*cmdsize [0-9]+\n\s*path ([^(]+?)\s*\(offset"DEPS_REGEX = r"^\s*([^(\n]+?)\s*\("VAR_REGEX = r"^\s*(?:__DATA|__DATA_CONST)\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)"FUNC_REGEX = r"^\s*(?!__DATA|__DATA_CONST)[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)"CLASS_REGEX = r"^\s*[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+[A-Za-z_]+\s+[0-9]+\s+([A-Za-z0-9_]+)\s+(_OBJC_[A-Za-z0-9_$]+)"NM_REGEX = r"^[0-9A-Fa-f]+\s+T\s+([A-Za-z_0-9$]+)"LAZY_REGEX = r"^\s*[A-Za-z0-9_]+\s+[A-Za-z0-9_]+\s+0x[0-9a-fA-F]+\s+0x[0-9a-fA-F]+\s+([A-Za-z0-9_]+)\s+((?!_OBJC_)[A-Za-z0-9_$]+)"
RPATHS: Dict[str, List[str]] = {}DEPS: Dict[str, List[str]] = {}NM_OVERRIDES: Dict[str, List[str]] = {}
class ResolutionError(Exception): def __init__(self, message: str) -> None: self.message = message super().__init__(message)
class SymbolType(Enum): VARIABLE = 1 FUNCTION = 2 OBJC_CLASS = 3
class Symbol: def __init__(self, binary: str, name: str, type: SymbolType) -> None: self.binary = binary self.name = name self.type = type def __eq__(self, other: object) -> bool: return isinstance(other, Symbol) and other.name == self.name and other.binary == self.binary def __hash__(self) -> int: return hash((self.binary, self.name)) def __str__(self) -> str: return f'({self.binary}, {self.name}, {self.type})' def __repr__(self) -> str: return str(self)
def basename_no_ext(string: str) -> str: result = string while True: result, ext = os.path.splitext(result) if len(ext) == 0: break return result
def get_rpaths(binary: str, executable: str | None = None, extra_rpaths: List[str] | None = None, ignore_cache: bool = False) -> List[str]: if (not ignore_cache) and (binary in RPATHS): return RPATHS[binary]
output = subprocess.check_output([OTOOL, '-l', binary]).decode() matches = re.finditer(RPATH_REGEX, output, re.MULTILINE) rpaths = [match.group(1) for match in matches]
if extra_rpaths != None: rpaths += extra_rpaths
final_rpaths: List[str] = [] for rpath in rpaths: parts = pathlib.PurePosixPath(rpath).parts if parts[0] == '@executable_path' or parts[0] == '@executable': if executable == None: raise ResolutionError('Cannot use @executable_path when no executable is specified') else: final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:]))) elif parts[0] == '@loader_path': final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:]))) elif parts[0].startswith('@'): raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in RPATH: ' + rpath) else: if rpath.startswith(XCODE_PATH + '/') or rpath.startswith(SYSTEM_ROOT + '/'): final_rpaths.append(rpath) else: final_rpaths.append(os.path.normpath(os.path.join(SYSTEM_ROOT, os.path.relpath(rpath, '/'))))
for template in TRY_PATH_TEMPLATES: path = template.format(os.path.dirname(binary)) if os.path.exists(path): final_rpaths.append(os.path.normpath(path))
final_rpaths = sorted(set(final_rpaths))
RPATHS[binary] = final_rpaths return final_rpaths
def generate_binary_list(xcode_path: str): # this is used to collect the root binaries for the search; # the Xcode binary in `MacOS` covers most things, but plug-ins are dynamically loaded by # Xcode at runtime, so we won't automatically pull them in with our search. instead, we # scan them as roots as well. bins: List[str] = [XCODE_MAIN_BINARY] for plugin in os.listdir(os.path.join(xcode_path, 'Contents', 'PlugIns')): full_path = os.path.join(xcode_path, 'Contents', 'PlugIns', plugin) basename = os.path.splitext(plugin)[0] fw_path = os.path.join(full_path, 'Versions', 'A', basename) bundle_path = os.path.join(full_path, 'Contents', 'MacOS', basename) if os.path.exists(fw_path): bins.append(fw_path) elif os.path.exists(bundle_path): bins.append(bundle_path) return bins
def get_deps(binary: str, recursive: bool = False, executable: str | None = None, processed: List[str] = []) -> List[str]: if binary in processed: return []
processed.append(binary)
output = subprocess.check_output([OTOOL, '-L', binary]).decode() matches = re.finditer(DEPS_REGEX, output, re.MULTILINE) deps = [match.group(1) for match in matches]
final_deps: List[str] = [] rpaths = get_rpaths(binary)
for dep in deps: parts = pathlib.PurePosixPath(dep).parts if parts[0] == '@executable_path' or parts[0] == '@executable': if executable == None: raise ResolutionError('Cannot use @executable_path when no executable is specified') else: final_deps.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:]))) elif parts[0] == '@loader_path': final_deps.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:]))) elif parts[0] == '@rpath': found = False for rpath in rpaths: if os.path.exists(os.path.join(rpath, *parts[1:])): final_deps.append(os.path.normpath(os.path.join(rpath, *parts[1:]))) found = True break if not found: raise ResolutionError('Could not find dependency in RPATH: ' + dep + '\nSearched: ' + str(rpaths)) elif parts[0].startswith('@'): raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in dependency: ' + dep) else: if dep.startswith('/Applications/Xcode.app/'): final_deps.append(os.path.join(XCODE_PATH, *parts[3:])) else: final_deps.append(os.path.normpath(dep))
final_deps = sorted(set(final_deps))
# some binaries reference themselves as dependencies (for some reason). fix that. if binary in final_deps: final_deps.remove(binary)
# get rid of some repetitive duplicated dependencies final_deps = [x for x in final_deps if not os.path.basename(x) in EXCLUDED_BINARIES or x.startswith(SYSTEM_ROOT) or x.startswith('/usr')]
DEPS[binary] = final_deps
if recursive: recursive_deps = final_deps.copy() for dep in final_deps: if dep.startswith(XCODE_PATH + '/'): # overwrite the RPATH cache for the dependency since we're loading it get_rpaths(dep, executable, extra_rpaths = EXTRA_PLUGIN_RPATHS + rpaths, ignore_cache = True) try: recursive_deps += get_deps(dep, True, executable, processed) except ResolutionError as e: raise ResolutionError('Encountered error while processing dependency "' + dep + '" of "' + binary + '":\n' + e.message) recursive_deps = sorted(set(recursive_deps)) return recursive_deps else: return final_deps
def get_bind_symbols(binary: str, binary_map: Dict[str, str]) -> List[Symbol]: get_bin_name = lambda name: binary_map[name] if name in binary_map else f'@{name}@' output = subprocess.check_output([DYLDINFO, '-bind', binary]).decode() var_matches = re.finditer(VAR_REGEX, output, re.MULTILINE) vars = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.VARIABLE) for match in var_matches)) func_matches = re.finditer(FUNC_REGEX, output, re.MULTILINE) funcs = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in func_matches)) class_matches = re.finditer(CLASS_REGEX, output, re.MULTILINE) classes = list(set(Symbol(get_bin_name(match.group(1)), match.group(2)[len('_OBJC_CLASS_$_'):], SymbolType.OBJC_CLASS) for match in class_matches if match.group(2).startswith('_OBJC_CLASS_$_')))
# most lazy symbols are functions, so let's assume that and override manually as necessary output = subprocess.check_output([DYLDINFO, '-lazy_bind', binary]).decode() matches = re.finditer(LAZY_REGEX, output, re.MULTILINE) lazy = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in matches))
symbols = vars + funcs + classes + lazy
for symbol in symbols: if symbol.binary.startswith('@') and symbol.binary.endswith('@'): continue if not symbol.binary in NM_OVERRIDES: if os.path.exists(SYSTEM_ROOT + symbol.binary) or os.path.exists(symbol.binary): output = subprocess.check_output([NM, '-Ug', '-arch', 'x86_64', (SYSTEM_ROOT + symbol.binary) if os.path.exists(SYSTEM_ROOT + symbol.binary) else symbol.binary]).decode() matches = re.finditer(NM_REGEX, output, re.MULTILINE) NM_OVERRIDES[symbol.binary] = [x.group(1) for x in matches] else: NM_OVERRIDES[symbol.binary] = [] if symbol.name in NM_OVERRIDES[symbol.binary]: symbol.type = SymbolType.FUNCTION
return symbols
def generate_method(method: str) -> str: type = method[0] signature = '' split = method[1:].split(':') if len(split) == 1: signature = split[0] else: for previous, current in zip(split, split[1:]): if len(signature) > 0: signature += ' ' signature += f'{previous}: (id){previous}' return '\n\n' + TEMPLATE_METHOD.format(type, signature).strip()
def main(): bins = generate_binary_list(XCODE_PATH)
try: # cache the RPATHs for the root binaries for bin in bins: get_rpaths(bin, executable = XCODE_MAIN_BINARY, extra_rpaths = None if bin == XCODE_MAIN_BINARY else EXTRA_PLUGIN_RPATHS)
all_bins: List[str] = [] system_bins: List[str] = [] xc_bins: List[str] = []
# collect all the binaries that are loaded for bin in bins: all_bins.append(bin) all_bins += get_deps(bin, True, XCODE_MAIN_BINARY)
all_bins = sorted(set(all_bins))
system_bins_tmp = [x for x in all_bins if not x.startswith(XCODE_PATH + '/')] xc_bins = sorted(set(all_bins).difference(set(system_bins_tmp))) system_bins = sorted(x[len(SYSTEM_ROOT):] if x.startswith(SYSTEM_ROOT + '/') else x for x in system_bins_tmp)
for bin in system_bins: print(bin)
binary_map = {basename_no_ext(os.path.basename(x)): x for x in all_bins} important_bins = [x for x in all_bins if basename_no_ext(os.path.basename(x)) in IMPORTANT_BINARIES]
# now let's get a list of all the symbols required at load time (and their associated binaries) all_symbols: List[Symbol] = [] for bin in xc_bins: all_symbols += get_bind_symbols(bin, binary_map) for bin in system_bins: short_bin_name = basename_no_ext(os.path.basename(bin)) if not short_bin_name in IMPORTANT_BINARIES: if os.path.exists(SYSTEM_ROOT + bin): all_symbols += get_bind_symbols(SYSTEM_ROOT + bin, binary_map)
all_symbols = list(set(all_symbols)) system_symbols = [x for x in all_symbols if x.binary in system_bins] important_symbols = [x for x in all_symbols if x.binary in important_bins]
for symbol in important_symbols: bin_name = basename_no_ext(os.path.basename(symbol.binary)) if symbol.name in FUNC_OVERRIDES[bin_name] or symbol.name[1:] in FUNC_OVERRIDES[bin_name]: symbol.type = SymbolType.FUNCTION
important_symbols_separated: Dict[str, List[Symbol]] = {}
for important_bin in IMPORTANT_BINARIES: important_symbols_separated[important_bin] = [x for x in important_symbols if basename_no_ext(os.path.basename(x.binary)) == important_bin]
for important_bin, symbols in important_symbols_separated.items(): THIS_DIR = os.path.join(OUT_DIR, important_bin) shutil.rmtree(THIS_DIR, ignore_errors=True) os.makedirs(THIS_DIR, exist_ok=True) classes = sorted(x.name for x in symbols if x.type == SymbolType.OBJC_CLASS) vars = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.VARIABLE) funcs = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.FUNCTION)
os.makedirs(os.path.join(THIS_DIR, 'src'), exist_ok=True) #os.makedirs(os.path.join(THIS_DIR, 'include'), exist_ok=True) #os.makedirs(os.path.join(THIS_DIR, 'include', important_bin), exist_ok=True)
with open(os.path.join(THIS_DIR, 'src', 'classes.m'), 'w') as classes_source: classes_source.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n') classes_source.write('#import <Foundation/Foundation.h>\n\n')
for klass in classes: classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + '\n\n' + TEMPLATE_IMPL.format(klass).strip()) if important_bin in ENSURE_METHODS and klass in ENSURE_METHODS[important_bin]: for method in ENSURE_METHODS[important_bin][klass]: classes_source.write(generate_method(method)) classes_source.write('\n\n@end\n\n')
if important_bin in ENSURE_METHODS: for klass in ENSURE_METHODS[important_bin]: if not klass in classes: classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + f'\n\n@implementation {klass}') for method in ENSURE_METHODS[important_bin][klass]: classes_source.write(generate_method(method)) classes_source.write('\n\n@end\n\n')
with open(os.path.join(THIS_DIR, 'CMakeLists.txt'), 'w') as cmake_txt: cmake_txt.write(CMAKE_TEMPLATE.format(important_bin, 'C' if important_bin == 'AppKit' else 'A').strip() + '\n')
with open(os.path.join(THIS_DIR, 'src', 'main.m'), 'w') as file: file.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n') file.write('#import <objc/NSObject.h>\n\n') file.write('@interface NSString : NSObject\n@end\n\n') if important_bin in PROLOGUES: file.write(PROLOGUES[important_bin].strip() + '\n\n') file.write(STUB_HEADER.strip() + '\n\n')
# most variables are just string keys, so default to that and then manually fix as necessary # # note that we don't really care what the value is in the string case (these are stubs, after all) for var in vars: if var in VAR_OVERRIDES[important_bin]: file.write(VAR_OVERRIDES[important_bin][var] + '\n') else: file.write(f'NSString *const {var} = @"{var}";\n')
file.write('\n')
for func in funcs: file.write(TEMPLATE_STUB.format(func).strip() + '\n\n')
file.write('\n')
if important_bin in ENSURE_FUNCS: for func in ENSURE_FUNCS[important_bin]: file.write(TEMPLATE_STUB.format(func).strip() + '\n\n')
except ResolutionError as e: print(e.message) exit(1)
if __name__ == '__main__': main()