this repo has no description
1#!/usr/bin/env python3
2
3import os
4from typing import List, Dict
5import subprocess
6import re
7import pathlib
8from enum import Enum
9import shutil
10import datetime
11
12SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
13
14#
15# USING THIS SCRIPT:
16# * edit `DYLDINFO`, `OTOOL`, and `NM` to refer to the corresponding programs to analyze x86_64 Mach-Os.
17# * edit `XCODE_PATH` to point to the Xcode application.
18# * edit `SYSTEM_ROOT` to point to the root of a macOS or stock Darling installation.
19# * if desired, edit `OUT_DIR` to point to where the stub frameworks will be generated; the default is the best in most cases.
20#
21
22###
23# EDIT THESE TO USE SCRIPT
24###
25
26DYLDINFO = 'x86_64-apple-darwin11-dyldinfo'
27OTOOL = 'x86_64-apple-darwin11-otool'
28NM = 'x86_64-apple-darwin11-nm'
29
30XCODE_PATH = '/data/darling/Applications/Xcode.app'
31SYSTEM_ROOT = '/usr/local/libexec/darling'
32OUT_DIR = os.path.join(SCRIPT_DIR, '..', 'src', 'frameworks', 'dev-stubs')
33
34###
35# DON'T EDIT ANYTHING ELSE
36###
37
38#
39# most of this was just trial-and-errored until i got something that worked.
40# in particular, the RPATH computations are iffy, but they get the job done.
41#
42
43MACH_BINARY_MIME_TYPE = 'application/x-mach-binary'
44XCODE_MAIN_BINARY = os.path.join(XCODE_PATH, 'Contents', 'MacOS', 'Xcode')
45
46# extra RPATHs added to the RPATHs of plugins (not the main Xcode binary) and their dependencies.
47EXTRA_PLUGIN_RPATHS = [
48 os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks'),
49 os.path.join(XCODE_PATH, 'Contents', 'OtherFrameworks'),
50 os.path.join(XCODE_PATH, 'Contents', 'Frameworks'),
51 os.path.join(XCODE_PATH, 'Contents', 'PlugIns'),
52 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'Frameworks'),
53 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'usr', 'lib'),
54 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Library', 'Frameworks'),
55 os.path.join(XCODE_PATH, 'Contents', 'Developer', 'Platforms', 'MacOSX.platform', 'Developer', 'Library', 'PrivateFrameworks'),
56
57 os.path.join(XCODE_PATH, 'Contents', 'PlugIns', 'Xcode3Core.ideplugin', 'Contents', 'Frameworks'),
58 os.path.join(XCODE_PATH, 'Contents', 'SharedFrameworks', 'XCBuild.framework', 'Versions', 'A', 'Frameworks'),
59]
60
61# RPATH templates to try to add for each binary, if they exist.
62# `{}` is substituted with the full path to the binary.
63TRY_PATH_TEMPLATES = [
64 '{}/../Frameworks',
65 '{}/Frameworks',
66 '{}/../SharedFrameworks',
67 '{}/SharedFrameworks',
68 '{}/../OtherFrameworks',
69 '{}/OtherFrameworks',
70 '{}/../SystemFrameworks',
71 '{}/SystemFrameworks',
72 '{}/../PlugIns',
73 '{}/PlugIns',
74]
75
76# these are binaries that are repeated often throughout the source and we don't really care about them.
77# it's mainly just lots of copies of the Swift system libraries.
78EXCLUDED_BINARIES = [
79 'libswiftAccelerate.dylib',
80 'libswiftAppKit.dylib',
81 'libswiftAVFoundation.dylib',
82 'libswiftCloudKit.dylib',
83 'libswiftCompression.dylib',
84 'libswiftContacts.dylib',
85 'libswiftCoreAudio.dylib',
86 'libswiftCoreData.dylib',
87 'libswiftCore.dylib',
88 'libswiftCoreFoundation.dylib',
89 'libswiftCoreGraphics.dylib',
90 'libswiftCoreImage.dylib',
91 'libswiftCoreLocation.dylib',
92 'libswiftCoreMedia.dylib',
93 'libswiftCoreMIDI.dylib',
94 'libswiftCryptoTokenKit.dylib',
95 'libswiftDarwin.dylib',
96 'libswift_Differentiation.dylib',
97 'libswiftDispatch.dylib',
98 'libswiftFoundation.dylib',
99 'libswiftGameplayKit.dylib',
100 'libswiftGLKit.dylib',
101 'libswiftIntents.dylib',
102 'libswiftIOKit.dylib',
103 'libswiftMapKit.dylib',
104 'libswiftMetal.dylib',
105 'libswiftMetalKit.dylib',
106 'libswiftModelIO.dylib',
107 'libswiftNaturalLanguage.dylib',
108 'libswiftNetwork.dylib',
109 'libswiftObjectiveC.dylib',
110 'libswiftOpenCL.dylib',
111 'libswiftos.dylib',
112 'libswiftPhotos.dylib',
113 'libswiftQuartzCore.dylib',
114 'libswiftRemoteMirror.dylib',
115 'libswiftSafariServices.dylib',
116 'libswiftSceneKit.dylib',
117 'libswiftsimd.dylib',
118 'libswiftSpriteKit.dylib',
119 'libswiftSwiftLang.dylib',
120 'libswiftSwiftOnoneSupport.dylib',
121 'libswiftVision.dylib',
122 'libswiftXCTest.dylib',
123 'libswiftXPC.dylib',
124]
125
126# these are the short names for the binaries we want to generate stubs for
127IMPORTANT_BINARIES = [
128 'AppKit',
129 'AudioToolbox',
130 'Cocoa',
131 'CoreData',
132 'CoreGraphics',
133 'CoreText',
134 'ImageIO',
135 'OpenGL',
136 'QuartzCore',
137]
138
139COPYRIGHT_HEADER = """
140/*
141 * This file is part of Darling.
142 *
143 * Copyright (C) {} Darling Developers
144 *
145 * Darling is free software: you can redistribute it and/or modify
146 * it under the terms of the GNU General Public License as published by
147 * the Free Software Foundation, either version 3 of the License, or
148 * (at your option) any later version.
149 *
150 * Darling is distributed in the hope that it will be useful,
151 * but WITHOUT ANY WARRANTY; without even the implied warranty of
152 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
153 * GNU General Public License for more details.
154 *
155 * You should have received a copy of the GNU General Public License
156 * along with Darling. If not, see <http://www.gnu.org/licenses/>.
157 */
158"""
159
160STUB_HEADER = """
161#include <stdlib.h>
162#include <stdio.h>
163
164static int verbose = 0;
165
166__attribute__((constructor))
167static void initme(void) {
168 verbose = getenv("STUB_VERBOSE") != NULL;
169}
170
171void __simple_kprintf(const char* format, ...) __attribute__((format(printf, 1, 2)));
172
173#define LOG_FUNC __simple_kprintf
174"""
175
176TEMPLATE_STUB = """
177void* {0}(void) {{
178 if (verbose) LOG_FUNC("STUB: {0} called\\n");
179 return NULL;
180}};
181"""
182
183TEMPLATE_INTERFACE = """
184@interface {} : NSObject
185@end
186"""
187
188TEMPLATE_CATEGORY = """
189@interface {}
190@end
191"""
192
193TEMPLATE_IMPL = """
194@implementation {}
195
196- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
197{{
198 return [NSMethodSignature signatureWithObjCTypes: "v@:"];
199}}
200
201- (void)forwardInvocation:(NSInvocation *)anInvocation
202{{
203 NSLog(@"Stub called: %@ in %@", NSStringFromSelector([anInvocation selector]), [self class]);
204}}
205
206"""
207
208TEMPLATE_METHOD = """
209{0} (id){1}
210{{
211 NSLog(@"Stub called: %@ in %@", NSStringFromSelector(_cmd), [self class]);
212 return nil;
213}}
214"""
215
216CMAKE_TEMPLATE = """
217project({0}_stub)
218
219set(DYLIB_COMPAT_VERSION "1.0.0")
220set(DYLIB_CURRENT_VERSION "1.0.0")
221set(FRAMEWORK_VERSION "{1}")
222
223add_framework({0}
224 FAT
225 CURRENT_VERSION
226 VERSION ${{FRAMEWORK_VERSION}}
227 TARGET_NAME {0}${{STUB_SUFFIX}}
228 ${{NO_INSTALL_ARG}}
229
230 SOURCES
231 src/classes.m
232 src/main.m
233
234 DEPENDENCIES
235 system
236 Foundation
237)
238
239#target_include_directories({0}${{STUB_SUFFIX}} BEFORE PRIVATE include)
240"""
241
242# most variables are just string keys, so that's the default.
243# these overrides for the definitions of certain variables prevent them from being defined as strings and instead define them with the appropriate values.
244VAR_OVERRIDES = {
245 'AppKit': {
246 'NSUnderlineByWordMask': 'NSUInteger NSUnderlineByWordMask = 0x8000;',
247 'NSApp': 'NSObject *NSApp = nil;',
248 'NSFontWeightBold': 'const CGFloat NSFontWeightBold = 0x3fd99999a0000000;',
249 'NSFontWeightMedium': 'const CGFloat NSFontWeightMedium = 0x3fcd70a3e0000000;',
250 'NSFontWeightRegular': 'const CGFloat NSFontWeightRegular = 0x0000000000000000;',
251 'NSFontWeightSemibold': 'const CGFloat NSFontWeightSemibold = 0x3fd3333340000000;',
252 'NSFontWeightThin': 'const CGFloat NSFontWeightThin = 0xbfe3333340000000;',
253 'NSFontWeightLight': 'const CGFloat NSFontWeightLight = 0xbfd99999a0000000;',
254 'NSFontWeightUltraLight': 'const CGFloat NSFontWeightUltraLight = 0xbfe99999a0000000;',
255 'NSFontWeightBlack': 'const CGFloat NSFontWeightBlack = 0x3fe3d70a40000000;',
256 'NSFontWeightHeavy': 'const CGFloat NSFontWeightHeavy = 0x3fe1eb8520000000;',
257 'NSViewNoIntrinsicMetric': 'const CGFloat NSViewNoIntrinsicMetric = 0xbff0000000000000;',
258 'NSAppKitVersionNumber': 'const double NSAppKitVersionNumber = 1504;',
259 },
260 'CoreGraphics': {
261 'CGRectZero': 'const CGRect CGRectZero = {{0, 0}, {0, 0}};',
262 'CGRectNull': 'const CGRect CGRectNull = {{INFINITY, INFINITY}, {0, 0}};',
263 'CGPointZero': 'const CGPoint CGPointZero = {0, 0};',
264 'CGSizeZero': 'const CGSize CGSizeZero = {0, 0};',
265 'CGRectInfinite': 'const CGRect CGRectInfinite = {{0, 0}, {INFINITY, INFINITY}};',
266 'kCGDisplayPixelHeight': 'unsigned int kCGDisplayPixelHeight = 1;',
267 'kCGDisplayPixelWidth': 'unsigned int kCGDisplayPixelWidth = 1;',
268 },
269 'QuartzCore': {
270 'CATransform3DIdentity': 'const CATransform3D CATransform3DIdentity = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};'
271 },
272}
273
274# sometimes, functions are incorrectly detected as variables. this can be used to override certain symbols as functions in those cases.
275# note that this should rarely be necessary, since we double-check with `nm`.
276FUNC_OVERRIDES = {
277 #'CoreGraphics': [
278 # 'CGRectGetMinX',
279 # 'CGRectGetWidth',
280 #],
281}
282
283# this is necessary to ensure certain functions that we need but Xcode doesn't need still get stubbed.
284ENSURE_FUNCS = {
285 'CoreGraphics': [
286 'CGSSetDenyWindowServerConnections',
287 ],
288}
289
290# this is necessary to ensure certain methods get stubbed as individual methods.
291# for objc, we can stub using an invocation forwarding method. this is fine for the vast majority of cases.
292# however, Xcode swizzles some methods, so they have to exist as individual methods.
293ENSURE_METHODS = {
294 'AppKit': {
295 'NSColorWell': [
296 '-activate:',
297 '-deactivate',
298 ],
299 'NSObject (BindingSupport)': [
300 '-bind:toObject:withKeyPath:options:',
301 '-infoForBinding:',
302 '-unbind:',
303 ],
304 'NSObject (NSKeyValueBindingCreation)': [
305 '-exposedBindings',
306 ],
307 },
308}
309
310# this is added to the top of the `main.m` source file for the corresponding framework.
311PROLOGUES = {
312 'AppKit': """
313#import <CoreGraphics/CGGeometry.h>
314""",
315
316 'CoreGraphics': """
317#include <math.h>
318
319#ifdef __LP64__
320 typedef double CGFloat;
321#else
322 typedef float CGFloat;
323#endif
324
325struct CGPoint {
326 CGFloat x;
327 CGFloat y;
328};
329typedef struct CGPoint CGPoint;
330
331struct CGSize {
332 CGFloat width;
333 CGFloat height;
334};
335typedef struct CGSize CGSize;
336
337struct CGVector {
338 CGFloat dx;
339 CGFloat dy;
340};
341typedef struct CGVector CGVector;
342
343struct CGRect {
344 CGPoint origin;
345 CGSize size;
346};
347typedef struct CGRect CGRect;
348""",
349
350 'QuartzCore': """
351#ifdef __LP64__
352 typedef double CGFloat;
353#else
354 typedef float CGFloat;
355#endif
356
357typedef struct {
358 CGFloat m11, m12, m13, m14;
359 CGFloat m21, m22, m23, m24;
360 CGFloat m31, m32, m33, m34;
361 CGFloat m41, m42, m43, m44;
362} CATransform3D;
363""",
364}
365
366for bin in IMPORTANT_BINARIES:
367 if not bin in VAR_OVERRIDES:
368 VAR_OVERRIDES[bin] = {}
369 if not bin in FUNC_OVERRIDES:
370 FUNC_OVERRIDES[bin] = []
371
372RPATH_REGEX = r"\s*cmd LC_RPATH\n\s*cmdsize [0-9]+\n\s*path ([^(]+?)\s*\(offset"
373DEPS_REGEX = r"^\s*([^(\n]+?)\s*\("
374VAR_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_$]+)"
375FUNC_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_$]+)"
376CLASS_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_$]+)"
377NM_REGEX = r"^[0-9A-Fa-f]+\s+T\s+([A-Za-z_0-9$]+)"
378LAZY_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_$]+)"
379
380RPATHS: Dict[str, List[str]] = {}
381DEPS: Dict[str, List[str]] = {}
382NM_OVERRIDES: Dict[str, List[str]] = {}
383
384class ResolutionError(Exception):
385 def __init__(self, message: str) -> None:
386 self.message = message
387 super().__init__(message)
388
389class SymbolType(Enum):
390 VARIABLE = 1
391 FUNCTION = 2
392 OBJC_CLASS = 3
393
394class Symbol:
395 def __init__(self, binary: str, name: str, type: SymbolType) -> None:
396 self.binary = binary
397 self.name = name
398 self.type = type
399 def __eq__(self, other: object) -> bool:
400 return isinstance(other, Symbol) and other.name == self.name and other.binary == self.binary
401 def __hash__(self) -> int:
402 return hash((self.binary, self.name))
403 def __str__(self) -> str:
404 return f'({self.binary}, {self.name}, {self.type})'
405 def __repr__(self) -> str:
406 return str(self)
407
408def basename_no_ext(string: str) -> str:
409 result = string
410 while True:
411 result, ext = os.path.splitext(result)
412 if len(ext) == 0:
413 break
414 return result
415
416def get_rpaths(binary: str, executable: str | None = None, extra_rpaths: List[str] | None = None, ignore_cache: bool = False) -> List[str]:
417 if (not ignore_cache) and (binary in RPATHS):
418 return RPATHS[binary]
419
420 output = subprocess.check_output([OTOOL, '-l', binary]).decode()
421 matches = re.finditer(RPATH_REGEX, output, re.MULTILINE)
422 rpaths = [match.group(1) for match in matches]
423
424 if extra_rpaths != None:
425 rpaths += extra_rpaths
426
427 final_rpaths: List[str] = []
428 for rpath in rpaths:
429 parts = pathlib.PurePosixPath(rpath).parts
430 if parts[0] == '@executable_path' or parts[0] == '@executable':
431 if executable == None:
432 raise ResolutionError('Cannot use @executable_path when no executable is specified')
433 else:
434 final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:])))
435 elif parts[0] == '@loader_path':
436 final_rpaths.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:])))
437 elif parts[0].startswith('@'):
438 raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in RPATH: ' + rpath)
439 else:
440 if rpath.startswith(XCODE_PATH + '/') or rpath.startswith(SYSTEM_ROOT + '/'):
441 final_rpaths.append(rpath)
442 else:
443 final_rpaths.append(os.path.normpath(os.path.join(SYSTEM_ROOT, os.path.relpath(rpath, '/'))))
444
445 for template in TRY_PATH_TEMPLATES:
446 path = template.format(os.path.dirname(binary))
447 if os.path.exists(path):
448 final_rpaths.append(os.path.normpath(path))
449
450 final_rpaths = sorted(set(final_rpaths))
451
452 RPATHS[binary] = final_rpaths
453 return final_rpaths
454
455def generate_binary_list(xcode_path: str):
456 # this is used to collect the root binaries for the search;
457 # the Xcode binary in `MacOS` covers most things, but plug-ins are dynamically loaded by
458 # Xcode at runtime, so we won't automatically pull them in with our search. instead, we
459 # scan them as roots as well.
460 bins: List[str] = [XCODE_MAIN_BINARY]
461 for plugin in os.listdir(os.path.join(xcode_path, 'Contents', 'PlugIns')):
462 full_path = os.path.join(xcode_path, 'Contents', 'PlugIns', plugin)
463 basename = os.path.splitext(plugin)[0]
464 fw_path = os.path.join(full_path, 'Versions', 'A', basename)
465 bundle_path = os.path.join(full_path, 'Contents', 'MacOS', basename)
466 if os.path.exists(fw_path):
467 bins.append(fw_path)
468 elif os.path.exists(bundle_path):
469 bins.append(bundle_path)
470 return bins
471
472def get_deps(binary: str, recursive: bool = False, executable: str | None = None, processed: List[str] = []) -> List[str]:
473 if binary in processed:
474 return []
475
476 processed.append(binary)
477
478 output = subprocess.check_output([OTOOL, '-L', binary]).decode()
479 matches = re.finditer(DEPS_REGEX, output, re.MULTILINE)
480 deps = [match.group(1) for match in matches]
481
482 final_deps: List[str] = []
483 rpaths = get_rpaths(binary)
484
485 for dep in deps:
486 parts = pathlib.PurePosixPath(dep).parts
487 if parts[0] == '@executable_path' or parts[0] == '@executable':
488 if executable == None:
489 raise ResolutionError('Cannot use @executable_path when no executable is specified')
490 else:
491 final_deps.append(os.path.normpath(os.path.join(os.path.dirname(executable), *parts[1:])))
492 elif parts[0] == '@loader_path':
493 final_deps.append(os.path.normpath(os.path.join(os.path.dirname(binary), *parts[1:])))
494 elif parts[0] == '@rpath':
495 found = False
496 for rpath in rpaths:
497 if os.path.exists(os.path.join(rpath, *parts[1:])):
498 final_deps.append(os.path.normpath(os.path.join(rpath, *parts[1:])))
499 found = True
500 break
501 if not found:
502 raise ResolutionError('Could not find dependency in RPATH: ' + dep + '\nSearched: ' + str(rpaths))
503 elif parts[0].startswith('@'):
504 raise ResolutionError('Unrecognized special path root (' + parts[0] + ') in dependency: ' + dep)
505 else:
506 if dep.startswith('/Applications/Xcode.app/'):
507 final_deps.append(os.path.join(XCODE_PATH, *parts[3:]))
508 else:
509 final_deps.append(os.path.normpath(dep))
510
511 final_deps = sorted(set(final_deps))
512
513 # some binaries reference themselves as dependencies (for some reason). fix that.
514 if binary in final_deps:
515 final_deps.remove(binary)
516
517 # get rid of some repetitive duplicated dependencies
518 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')]
519
520 DEPS[binary] = final_deps
521
522 if recursive:
523 recursive_deps = final_deps.copy()
524 for dep in final_deps:
525 if dep.startswith(XCODE_PATH + '/'):
526 # overwrite the RPATH cache for the dependency since we're loading it
527 get_rpaths(dep, executable, extra_rpaths = EXTRA_PLUGIN_RPATHS + rpaths, ignore_cache = True)
528 try:
529 recursive_deps += get_deps(dep, True, executable, processed)
530 except ResolutionError as e:
531 raise ResolutionError('Encountered error while processing dependency "' + dep + '" of "' + binary + '":\n' + e.message)
532 recursive_deps = sorted(set(recursive_deps))
533 return recursive_deps
534 else:
535 return final_deps
536
537def get_bind_symbols(binary: str, binary_map: Dict[str, str]) -> List[Symbol]:
538 get_bin_name = lambda name: binary_map[name] if name in binary_map else f'@{name}@'
539 output = subprocess.check_output([DYLDINFO, '-bind', binary]).decode()
540 var_matches = re.finditer(VAR_REGEX, output, re.MULTILINE)
541 vars = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.VARIABLE) for match in var_matches))
542 func_matches = re.finditer(FUNC_REGEX, output, re.MULTILINE)
543 funcs = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in func_matches))
544 class_matches = re.finditer(CLASS_REGEX, output, re.MULTILINE)
545 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_$_')))
546
547 # most lazy symbols are functions, so let's assume that and override manually as necessary
548 output = subprocess.check_output([DYLDINFO, '-lazy_bind', binary]).decode()
549 matches = re.finditer(LAZY_REGEX, output, re.MULTILINE)
550 lazy = list(set(Symbol(get_bin_name(match.group(1)), match.group(2), SymbolType.FUNCTION) for match in matches))
551
552 symbols = vars + funcs + classes + lazy
553
554 for symbol in symbols:
555 if symbol.binary.startswith('@') and symbol.binary.endswith('@'):
556 continue
557 if not symbol.binary in NM_OVERRIDES:
558 if os.path.exists(SYSTEM_ROOT + symbol.binary) or os.path.exists(symbol.binary):
559 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()
560 matches = re.finditer(NM_REGEX, output, re.MULTILINE)
561 NM_OVERRIDES[symbol.binary] = [x.group(1) for x in matches]
562 else:
563 NM_OVERRIDES[symbol.binary] = []
564 if symbol.name in NM_OVERRIDES[symbol.binary]:
565 symbol.type = SymbolType.FUNCTION
566
567 return symbols
568
569def generate_method(method: str) -> str:
570 type = method[0]
571 signature = ''
572 split = method[1:].split(':')
573 if len(split) == 1:
574 signature = split[0]
575 else:
576 for previous, current in zip(split, split[1:]):
577 if len(signature) > 0:
578 signature += ' '
579 signature += f'{previous}: (id){previous}'
580 return '\n\n' + TEMPLATE_METHOD.format(type, signature).strip()
581
582def main():
583 bins = generate_binary_list(XCODE_PATH)
584
585 try:
586 # cache the RPATHs for the root binaries
587 for bin in bins:
588 get_rpaths(bin, executable = XCODE_MAIN_BINARY, extra_rpaths = None if bin == XCODE_MAIN_BINARY else EXTRA_PLUGIN_RPATHS)
589
590 all_bins: List[str] = []
591 system_bins: List[str] = []
592 xc_bins: List[str] = []
593
594 # collect all the binaries that are loaded
595 for bin in bins:
596 all_bins.append(bin)
597 all_bins += get_deps(bin, True, XCODE_MAIN_BINARY)
598
599 all_bins = sorted(set(all_bins))
600
601 system_bins_tmp = [x for x in all_bins if not x.startswith(XCODE_PATH + '/')]
602 xc_bins = sorted(set(all_bins).difference(set(system_bins_tmp)))
603 system_bins = sorted(x[len(SYSTEM_ROOT):] if x.startswith(SYSTEM_ROOT + '/') else x for x in system_bins_tmp)
604
605 for bin in system_bins:
606 print(bin)
607
608 binary_map = {basename_no_ext(os.path.basename(x)): x for x in all_bins}
609 important_bins = [x for x in all_bins if basename_no_ext(os.path.basename(x)) in IMPORTANT_BINARIES]
610
611 # now let's get a list of all the symbols required at load time (and their associated binaries)
612 all_symbols: List[Symbol] = []
613 for bin in xc_bins:
614 all_symbols += get_bind_symbols(bin, binary_map)
615 for bin in system_bins:
616 short_bin_name = basename_no_ext(os.path.basename(bin))
617 if not short_bin_name in IMPORTANT_BINARIES:
618 if os.path.exists(SYSTEM_ROOT + bin):
619 all_symbols += get_bind_symbols(SYSTEM_ROOT + bin, binary_map)
620
621 all_symbols = list(set(all_symbols))
622 system_symbols = [x for x in all_symbols if x.binary in system_bins]
623 important_symbols = [x for x in all_symbols if x.binary in important_bins]
624
625 for symbol in important_symbols:
626 bin_name = basename_no_ext(os.path.basename(symbol.binary))
627 if symbol.name in FUNC_OVERRIDES[bin_name] or symbol.name[1:] in FUNC_OVERRIDES[bin_name]:
628 symbol.type = SymbolType.FUNCTION
629
630 important_symbols_separated: Dict[str, List[Symbol]] = {}
631
632 for important_bin in IMPORTANT_BINARIES:
633 important_symbols_separated[important_bin] = [x for x in important_symbols if basename_no_ext(os.path.basename(x.binary)) == important_bin]
634
635 for important_bin, symbols in important_symbols_separated.items():
636 THIS_DIR = os.path.join(OUT_DIR, important_bin)
637 shutil.rmtree(THIS_DIR, ignore_errors=True)
638 os.makedirs(THIS_DIR, exist_ok=True)
639 classes = sorted(x.name for x in symbols if x.type == SymbolType.OBJC_CLASS)
640 vars = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.VARIABLE)
641 funcs = sorted(x.name[1:] if x.name[0] == '_' else x.name for x in symbols if x.type == SymbolType.FUNCTION)
642
643 os.makedirs(os.path.join(THIS_DIR, 'src'), exist_ok=True)
644 #os.makedirs(os.path.join(THIS_DIR, 'include'), exist_ok=True)
645 #os.makedirs(os.path.join(THIS_DIR, 'include', important_bin), exist_ok=True)
646
647 with open(os.path.join(THIS_DIR, 'src', 'classes.m'), 'w') as classes_source:
648 classes_source.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n')
649 classes_source.write('#import <Foundation/Foundation.h>\n\n')
650
651 for klass in classes:
652 classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + '\n\n' + TEMPLATE_IMPL.format(klass).strip())
653 if important_bin in ENSURE_METHODS and klass in ENSURE_METHODS[important_bin]:
654 for method in ENSURE_METHODS[important_bin][klass]:
655 classes_source.write(generate_method(method))
656 classes_source.write('\n\n@end\n\n')
657
658 if important_bin in ENSURE_METHODS:
659 for klass in ENSURE_METHODS[important_bin]:
660 if not klass in classes:
661 classes_source.write((TEMPLATE_CATEGORY if klass.find('(') >= 0 else TEMPLATE_INTERFACE).format(klass).strip() + f'\n\n@implementation {klass}')
662 for method in ENSURE_METHODS[important_bin][klass]:
663 classes_source.write(generate_method(method))
664 classes_source.write('\n\n@end\n\n')
665
666 with open(os.path.join(THIS_DIR, 'CMakeLists.txt'), 'w') as cmake_txt:
667 cmake_txt.write(CMAKE_TEMPLATE.format(important_bin, 'C' if important_bin == 'AppKit' else 'A').strip() + '\n')
668
669 with open(os.path.join(THIS_DIR, 'src', 'main.m'), 'w') as file:
670 file.write(COPYRIGHT_HEADER.format(datetime.datetime.now(datetime.timezone.utc).year).strip() + '\n\n')
671 file.write('#import <objc/NSObject.h>\n\n')
672 file.write('@interface NSString : NSObject\n@end\n\n')
673 if important_bin in PROLOGUES:
674 file.write(PROLOGUES[important_bin].strip() + '\n\n')
675 file.write(STUB_HEADER.strip() + '\n\n')
676
677 # most variables are just string keys, so default to that and then manually fix as necessary
678 #
679 # note that we don't really care what the value is in the string case (these are stubs, after all)
680 for var in vars:
681 if var in VAR_OVERRIDES[important_bin]:
682 file.write(VAR_OVERRIDES[important_bin][var] + '\n')
683 else:
684 file.write(f'NSString *const {var} = @"{var}";\n')
685
686 file.write('\n')
687
688 for func in funcs:
689 file.write(TEMPLATE_STUB.format(func).strip() + '\n\n')
690
691 file.write('\n')
692
693 if important_bin in ENSURE_FUNCS:
694 for func in ENSURE_FUNCS[important_bin]:
695 file.write(TEMPLATE_STUB.format(func).strip() + '\n\n')
696
697 except ResolutionError as e:
698 print(e.message)
699 exit(1)
700
701if __name__ == '__main__':
702 main()