|
|
@@ -0,0 +1,609 @@
|
|
|
+import argparse
|
|
|
+import hashlib
|
|
|
+import os
|
|
|
+from pathlib import Path
|
|
|
+import platform
|
|
|
+import shutil
|
|
|
+import struct
|
|
|
+import subprocess
|
|
|
+from typing import List, Optional, Tuple
|
|
|
+
|
|
|
+parser = argparse.ArgumentParser(description="Fixup for MacOS application bundle")
|
|
|
+parser.add_argument("input_directory", help="Input directory (Application path)")
|
|
|
+parser.add_argument("executable_sub_path", help="Main executable sub path")
|
|
|
+
|
|
|
+# Use Apple LLVM on Darwin, otherwise standard LLVM.
|
|
|
+if platform.system() == "Darwin":
|
|
|
+ OTOOL = "otool"
|
|
|
+ INSTALL_NAME_TOOL = "install_name_tool"
|
|
|
+else:
|
|
|
+ OTOOL = shutil.which("llvm-otool")
|
|
|
+ if OTOOL is None:
|
|
|
+ for llvm_ver in [15, 14, 13]:
|
|
|
+ otool_path = shutil.which(f"llvm-otool-{llvm_ver}")
|
|
|
+ if otool_path is not None:
|
|
|
+ OTOOL = otool_path
|
|
|
+ INSTALL_NAME_TOOL = shutil.which(f"llvm-install-name-tool-{llvm_ver}")
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ INSTALL_NAME_TOOL = shutil.which("llvm-install-name-tool")
|
|
|
+
|
|
|
+
|
|
|
+args = parser.parse_args()
|
|
|
+
|
|
|
+
|
|
|
+def get_dylib_id(dylib_path: Path) -> str:
|
|
|
+ res = subprocess.check_output([OTOOL, "-D", str(dylib_path.absolute())]).decode(
|
|
|
+ "utf-8"
|
|
|
+ )
|
|
|
+
|
|
|
+ return res.split("\n")[1]
|
|
|
+
|
|
|
+
|
|
|
+def get_dylib_dependencies(dylib_path: Path) -> List[str]:
|
|
|
+ output = (
|
|
|
+ subprocess.check_output([OTOOL, "-L", str(dylib_path.absolute())])
|
|
|
+ .decode("utf-8")
|
|
|
+ .split("\n")[1:]
|
|
|
+ )
|
|
|
+
|
|
|
+ res = []
|
|
|
+
|
|
|
+ for line in output:
|
|
|
+ line = line.strip()
|
|
|
+ index = line.find(" (compatibility version ")
|
|
|
+ if index == -1:
|
|
|
+ continue
|
|
|
+
|
|
|
+ line = line[:index]
|
|
|
+
|
|
|
+ res.append(line)
|
|
|
+
|
|
|
+ return res
|
|
|
+
|
|
|
+
|
|
|
+def replace_dylib_id(dylib_path: Path, new_id: str):
|
|
|
+ subprocess.check_call(
|
|
|
+ [INSTALL_NAME_TOOL, "-id", new_id, str(dylib_path.absolute())]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def change_dylib_link(dylib_path: Path, old: str, new: str):
|
|
|
+ subprocess.check_call(
|
|
|
+ [INSTALL_NAME_TOOL, "-change", old, new, str(dylib_path.absolute())]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def add_dylib_rpath(dylib_path: Path, rpath: str):
|
|
|
+ subprocess.check_call(
|
|
|
+ [INSTALL_NAME_TOOL, "-add_rpath", rpath, str(dylib_path.absolute())]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def fixup_dylib(
|
|
|
+ dylib_path: Path,
|
|
|
+ replacement_path: str,
|
|
|
+ search_path: List[str],
|
|
|
+ content_directory: Path,
|
|
|
+):
|
|
|
+ dylib_id = get_dylib_id(dylib_path)
|
|
|
+ new_dylib_id = replacement_path + "/" + os.path.basename(dylib_id)
|
|
|
+ replace_dylib_id(dylib_path, new_dylib_id)
|
|
|
+
|
|
|
+ dylib_dependencies = get_dylib_dependencies(dylib_path)
|
|
|
+ dylib_new_mapping = {}
|
|
|
+
|
|
|
+ for dylib_dependency in dylib_dependencies:
|
|
|
+ if (
|
|
|
+ not dylib_dependency.startswith("@executable_path")
|
|
|
+ and not dylib_dependency.startswith("/usr/lib")
|
|
|
+ and not dylib_dependency.startswith("/System/Library")
|
|
|
+ ):
|
|
|
+ dylib_dependency_name = os.path.basename(dylib_dependency)
|
|
|
+ library_found = False
|
|
|
+ for library_base_path in search_path:
|
|
|
+ lib_path = Path(os.path.join(library_base_path, dylib_dependency_name))
|
|
|
+
|
|
|
+ if lib_path.exists():
|
|
|
+ target_replacement_path = get_path_related_to_target_exec(
|
|
|
+ content_directory, lib_path
|
|
|
+ )
|
|
|
+
|
|
|
+ dylib_new_mapping[dylib_dependency] = (
|
|
|
+ target_replacement_path
|
|
|
+ + "/"
|
|
|
+ + os.path.basename(dylib_dependency)
|
|
|
+ )
|
|
|
+ library_found = True
|
|
|
+
|
|
|
+ if not library_found:
|
|
|
+ raise Exception(
|
|
|
+ f"{dylib_id}: Cannot find dependency {dylib_dependency_name} for fixup"
|
|
|
+ )
|
|
|
+
|
|
|
+ for key in dylib_new_mapping:
|
|
|
+ change_dylib_link(dylib_path, key, dylib_new_mapping[key])
|
|
|
+
|
|
|
+
|
|
|
+FILE_TYPE_ASSEMBLY = 1
|
|
|
+
|
|
|
+ALIGN_REQUIREMENTS = 4096
|
|
|
+
|
|
|
+
|
|
|
+def parse_embedded_string(data: bytes) -> Tuple[bytes, str]:
|
|
|
+ first_byte = data[0]
|
|
|
+
|
|
|
+ if (first_byte & 0x80) == 0:
|
|
|
+ size = first_byte
|
|
|
+ data = data[1:]
|
|
|
+ else:
|
|
|
+ second_byte = data[1]
|
|
|
+
|
|
|
+ assert (second_byte & 0x80) == 0
|
|
|
+
|
|
|
+ size = (second_byte << 7) | (first_byte & 0x7F)
|
|
|
+
|
|
|
+ data = data[2:]
|
|
|
+
|
|
|
+ res = data[:size].decode("utf-8")
|
|
|
+ data = data[size:]
|
|
|
+
|
|
|
+ return (data, res)
|
|
|
+
|
|
|
+
|
|
|
+def write_embedded_string(file, string: str):
|
|
|
+ raw_str = string.encode("utf-8")
|
|
|
+ raw_str_len = len(raw_str)
|
|
|
+
|
|
|
+ assert raw_str_len < 0x7FFF
|
|
|
+
|
|
|
+ if raw_str_len > 0x7F:
|
|
|
+ file.write(struct.pack("b", raw_str_len & 0x7F | 0x80))
|
|
|
+ file.write(struct.pack("b", raw_str_len >> 7))
|
|
|
+ else:
|
|
|
+ file.write(struct.pack("b", raw_str_len))
|
|
|
+
|
|
|
+ file.write(raw_str)
|
|
|
+
|
|
|
+
|
|
|
+class BundleFileEntry(object):
|
|
|
+ offset: int
|
|
|
+ size: int
|
|
|
+ compressed_size: int
|
|
|
+ file_type: int
|
|
|
+ relative_path: str
|
|
|
+ data: bytes
|
|
|
+
|
|
|
+ def __init__(
|
|
|
+ self,
|
|
|
+ offset: int,
|
|
|
+ size: int,
|
|
|
+ compressed_size: int,
|
|
|
+ file_type: int,
|
|
|
+ relative_path: str,
|
|
|
+ data: bytes,
|
|
|
+ ) -> None:
|
|
|
+ self.offset = offset
|
|
|
+ self.size = size
|
|
|
+ self.compressed_size = compressed_size
|
|
|
+ self.file_type = file_type
|
|
|
+ self.relative_path = relative_path
|
|
|
+ self.data = data
|
|
|
+
|
|
|
+ def write(self, file):
|
|
|
+ self.offset = file.tell()
|
|
|
+
|
|
|
+ if (
|
|
|
+ self.file_type == FILE_TYPE_ASSEMBLY
|
|
|
+ and (self.offset % ALIGN_REQUIREMENTS) != 0
|
|
|
+ ):
|
|
|
+ padding_size = ALIGN_REQUIREMENTS - (self.offset % ALIGN_REQUIREMENTS)
|
|
|
+ file.write(b"\0" * padding_size)
|
|
|
+ self.offset += padding_size
|
|
|
+
|
|
|
+ file.write(self.data)
|
|
|
+
|
|
|
+ def write_header(self, file):
|
|
|
+ file.write(
|
|
|
+ struct.pack(
|
|
|
+ "QQQb", self.offset, self.size, self.compressed_size, self.file_type
|
|
|
+ )
|
|
|
+ )
|
|
|
+ write_embedded_string(file, self.relative_path)
|
|
|
+
|
|
|
+
|
|
|
+class BundleManifest(object):
|
|
|
+ major: int
|
|
|
+ minor: int
|
|
|
+ bundle_id: str
|
|
|
+ deps_json: BundleFileEntry
|
|
|
+ runtimeconfig_json: BundleFileEntry
|
|
|
+ flags: int
|
|
|
+ files: List[BundleFileEntry]
|
|
|
+
|
|
|
+ def __init__(
|
|
|
+ self,
|
|
|
+ major: int,
|
|
|
+ minor: int,
|
|
|
+ bundle_id: str,
|
|
|
+ deps_json: BundleFileEntry,
|
|
|
+ runtimeconfig_json: BundleFileEntry,
|
|
|
+ flags: int,
|
|
|
+ files: List[BundleFileEntry],
|
|
|
+ ) -> None:
|
|
|
+ self.major = major
|
|
|
+ self.minor = minor
|
|
|
+ self.bundle_id = bundle_id
|
|
|
+ self.deps_json = deps_json
|
|
|
+ self.runtimeconfig_json = runtimeconfig_json
|
|
|
+ self.flags = flags
|
|
|
+ self.files = files
|
|
|
+
|
|
|
+ def write(self, file) -> int:
|
|
|
+ for bundle_file in self.files:
|
|
|
+ bundle_file.write(file)
|
|
|
+
|
|
|
+ bundle_header_offset = file.tell()
|
|
|
+ file.write(struct.pack("iiI", self.major, self.minor, len(self.files)))
|
|
|
+ write_embedded_string(file, self.bundle_id)
|
|
|
+
|
|
|
+ if self.deps_json is not None:
|
|
|
+ deps_json_location_offset = self.deps_json.offset
|
|
|
+ deps_json_location_size = self.deps_json.size
|
|
|
+ else:
|
|
|
+ deps_json_location_offset = 0
|
|
|
+ deps_json_location_size = 0
|
|
|
+
|
|
|
+ if self.runtimeconfig_json is not None:
|
|
|
+ runtimeconfig_json_location_offset = self.runtimeconfig_json.offset
|
|
|
+ runtimeconfig_json_location_size = self.runtimeconfig_json.size
|
|
|
+ else:
|
|
|
+ runtimeconfig_json_location_offset = 0
|
|
|
+ runtimeconfig_json_location_size = 0
|
|
|
+
|
|
|
+ file.write(
|
|
|
+ struct.pack("qq", deps_json_location_offset, deps_json_location_size)
|
|
|
+ )
|
|
|
+ file.write(
|
|
|
+ struct.pack(
|
|
|
+ "qq",
|
|
|
+ runtimeconfig_json_location_offset,
|
|
|
+ runtimeconfig_json_location_size,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ file.write(struct.pack("q", self.flags))
|
|
|
+
|
|
|
+ for bundle_file in self.files:
|
|
|
+ bundle_file.write_header(file)
|
|
|
+
|
|
|
+ return bundle_header_offset
|
|
|
+
|
|
|
+
|
|
|
+def read_file_entry(
|
|
|
+ raw_data: bytes, header_bytes: bytes
|
|
|
+) -> Tuple[bytes, BundleFileEntry]:
|
|
|
+ (
|
|
|
+ offset,
|
|
|
+ size,
|
|
|
+ compressed_size,
|
|
|
+ file_type,
|
|
|
+ ) = struct.unpack("QQQb", header_bytes[:0x19])
|
|
|
+ (header_bytes, relative_path) = parse_embedded_string(header_bytes[0x19:])
|
|
|
+
|
|
|
+ target_size = compressed_size
|
|
|
+
|
|
|
+ if target_size == 0:
|
|
|
+ target_size = size
|
|
|
+
|
|
|
+ return (
|
|
|
+ header_bytes,
|
|
|
+ BundleFileEntry(
|
|
|
+ offset,
|
|
|
+ size,
|
|
|
+ compressed_size,
|
|
|
+ file_type,
|
|
|
+ relative_path,
|
|
|
+ raw_data[offset : offset + target_size],
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def get_dotnet_bundle_data(data: bytes) -> Optional[Tuple[int, int, BundleManifest]]:
|
|
|
+ offset = data.find(hashlib.sha256(b".net core bundle\n").digest())
|
|
|
+
|
|
|
+ if offset == -1:
|
|
|
+ return None
|
|
|
+
|
|
|
+ raw_header_offset = data[offset - 8 : offset]
|
|
|
+ (header_offset,) = struct.unpack("q", raw_header_offset)
|
|
|
+ header_bytes = data[header_offset:]
|
|
|
+
|
|
|
+ (
|
|
|
+ major,
|
|
|
+ minor,
|
|
|
+ files_count,
|
|
|
+ ) = struct.unpack("iiI", header_bytes[:0xC])
|
|
|
+ header_bytes = header_bytes[0xC:]
|
|
|
+
|
|
|
+ (header_bytes, bundle_id) = parse_embedded_string(header_bytes)
|
|
|
+
|
|
|
+ # v2 header
|
|
|
+ (
|
|
|
+ deps_json_location_offset,
|
|
|
+ deps_json_location_size,
|
|
|
+ ) = struct.unpack("qq", header_bytes[:0x10])
|
|
|
+ (
|
|
|
+ runtimeconfig_json_location_offset,
|
|
|
+ runtimeconfig_json_location_size,
|
|
|
+ ) = struct.unpack("qq", header_bytes[0x10:0x20])
|
|
|
+ (flags,) = struct.unpack("q", header_bytes[0x20:0x28])
|
|
|
+ header_bytes = header_bytes[0x28:]
|
|
|
+
|
|
|
+ files = []
|
|
|
+
|
|
|
+ deps_json = None
|
|
|
+ runtimeconfig_json = None
|
|
|
+
|
|
|
+ for _ in range(files_count):
|
|
|
+ (header_bytes, file_entry) = read_file_entry(data, header_bytes)
|
|
|
+
|
|
|
+ files.append(file_entry)
|
|
|
+
|
|
|
+ if file_entry.offset == deps_json_location_offset:
|
|
|
+ deps_json = file_entry
|
|
|
+ elif file_entry.offset == runtimeconfig_json_location_offset:
|
|
|
+ runtimeconfig_json = file_entry
|
|
|
+
|
|
|
+ file_entry = files[0]
|
|
|
+
|
|
|
+ return (
|
|
|
+ file_entry.offset,
|
|
|
+ header_offset,
|
|
|
+ BundleManifest(
|
|
|
+ major, minor, bundle_id, deps_json, runtimeconfig_json, flags, files
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+LC_SYMTAB = 0x2
|
|
|
+LC_SEGMENT_64 = 0x19
|
|
|
+LC_CODE_SIGNATURE = 0x1D
|
|
|
+
|
|
|
+
|
|
|
+def fixup_linkedit(file, data: bytes, new_size: int):
|
|
|
+ offset = 0
|
|
|
+
|
|
|
+ (
|
|
|
+ macho_magic,
|
|
|
+ macho_cputype,
|
|
|
+ macho_cpusubtype,
|
|
|
+ macho_filetype,
|
|
|
+ macho_ncmds,
|
|
|
+ macho_sizeofcmds,
|
|
|
+ macho_flags,
|
|
|
+ macho_reserved,
|
|
|
+ ) = struct.unpack("IiiIIIII", data[offset : offset + 0x20])
|
|
|
+
|
|
|
+ offset += 0x20
|
|
|
+
|
|
|
+ linkedit_offset = None
|
|
|
+ symtab_offset = None
|
|
|
+ codesign_offset = None
|
|
|
+
|
|
|
+ for _ in range(macho_ncmds):
|
|
|
+ (cmd, cmdsize) = struct.unpack("II", data[offset : offset + 8])
|
|
|
+
|
|
|
+ if cmd == LC_SEGMENT_64:
|
|
|
+ (
|
|
|
+ cmd,
|
|
|
+ cmdsize,
|
|
|
+ segname_raw,
|
|
|
+ vmaddr,
|
|
|
+ vmsize,
|
|
|
+ fileoff,
|
|
|
+ filesize,
|
|
|
+ maxprot,
|
|
|
+ initprot,
|
|
|
+ nsects,
|
|
|
+ flags,
|
|
|
+ ) = struct.unpack("II16sQQQQiiII", data[offset : offset + 72])
|
|
|
+ segname = segname_raw.decode("utf-8").split("\0")[0]
|
|
|
+
|
|
|
+ if segname == "__LINKEDIT":
|
|
|
+ linkedit_offset = offset
|
|
|
+ elif cmd == LC_SYMTAB:
|
|
|
+ symtab_offset = offset
|
|
|
+ elif cmd == LC_CODE_SIGNATURE:
|
|
|
+ codesign_offset = offset
|
|
|
+
|
|
|
+ offset += cmdsize
|
|
|
+ pass
|
|
|
+
|
|
|
+ assert linkedit_offset is not None and symtab_offset is not None
|
|
|
+
|
|
|
+ # If there is a codesign section, clean it up.
|
|
|
+ if codesign_offset is not None:
|
|
|
+ (
|
|
|
+ codesign_cmd,
|
|
|
+ codesign_cmdsize,
|
|
|
+ codesign_dataoff,
|
|
|
+ codesign_datasize,
|
|
|
+ ) = struct.unpack("IIII", data[codesign_offset : codesign_offset + 16])
|
|
|
+ file.seek(codesign_offset)
|
|
|
+ file.write(b"\0" * codesign_cmdsize)
|
|
|
+
|
|
|
+ macho_ncmds -= 1
|
|
|
+ macho_sizeofcmds -= codesign_cmdsize
|
|
|
+ file.seek(0)
|
|
|
+ file.write(
|
|
|
+ struct.pack(
|
|
|
+ "IiiIIIII",
|
|
|
+ macho_magic,
|
|
|
+ macho_cputype,
|
|
|
+ macho_cpusubtype,
|
|
|
+ macho_filetype,
|
|
|
+ macho_ncmds,
|
|
|
+ macho_sizeofcmds,
|
|
|
+ macho_flags,
|
|
|
+ macho_reserved,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ file.seek(codesign_dataoff)
|
|
|
+ file.write(b"\0" * codesign_datasize)
|
|
|
+
|
|
|
+ (
|
|
|
+ symtab_cmd,
|
|
|
+ symtab_cmdsize,
|
|
|
+ symtab_symoff,
|
|
|
+ symtab_nsyms,
|
|
|
+ symtab_stroff,
|
|
|
+ symtab_strsize,
|
|
|
+ ) = struct.unpack("IIIIII", data[symtab_offset : symtab_offset + 24])
|
|
|
+
|
|
|
+ symtab_strsize = new_size - symtab_stroff
|
|
|
+
|
|
|
+ new_symtab = struct.pack(
|
|
|
+ "IIIIII",
|
|
|
+ symtab_cmd,
|
|
|
+ symtab_cmdsize,
|
|
|
+ symtab_symoff,
|
|
|
+ symtab_nsyms,
|
|
|
+ symtab_stroff,
|
|
|
+ symtab_strsize,
|
|
|
+ )
|
|
|
+
|
|
|
+ file.seek(symtab_offset)
|
|
|
+ file.write(new_symtab)
|
|
|
+
|
|
|
+ (
|
|
|
+ linkedit_cmd,
|
|
|
+ linkedit_cmdsize,
|
|
|
+ linkedit_segname_raw,
|
|
|
+ linkedit_vmaddr,
|
|
|
+ linkedit_vmsize,
|
|
|
+ linkedit_fileoff,
|
|
|
+ linkedit_filesize,
|
|
|
+ linkedit_maxprot,
|
|
|
+ linkedit_initprot,
|
|
|
+ linkedit_nsects,
|
|
|
+ linkedit_flags,
|
|
|
+ ) = struct.unpack("II16sQQQQiiII", data[linkedit_offset : linkedit_offset + 72])
|
|
|
+
|
|
|
+ linkedit_filesize = new_size - linkedit_fileoff
|
|
|
+ linkedit_vmsize = linkedit_filesize
|
|
|
+
|
|
|
+ new_linkedit = struct.pack(
|
|
|
+ "II16sQQQQiiII",
|
|
|
+ linkedit_cmd,
|
|
|
+ linkedit_cmdsize,
|
|
|
+ linkedit_segname_raw,
|
|
|
+ linkedit_vmaddr,
|
|
|
+ linkedit_vmsize,
|
|
|
+ linkedit_fileoff,
|
|
|
+ linkedit_filesize,
|
|
|
+ linkedit_maxprot,
|
|
|
+ linkedit_initprot,
|
|
|
+ linkedit_nsects,
|
|
|
+ linkedit_flags,
|
|
|
+ )
|
|
|
+ file.seek(linkedit_offset)
|
|
|
+ file.write(new_linkedit)
|
|
|
+
|
|
|
+
|
|
|
+def write_bundle_data(
|
|
|
+ output,
|
|
|
+ old_bundle_base_offset: int,
|
|
|
+ new_bundle_base_offset: int,
|
|
|
+ bundle: BundleManifest,
|
|
|
+) -> int:
|
|
|
+ # Write bundle data
|
|
|
+ bundle_header_offset = bundle.write(output)
|
|
|
+ total_size = output.tell()
|
|
|
+
|
|
|
+ # Patch the header position
|
|
|
+ offset = file_data.find(hashlib.sha256(b".net core bundle\n").digest())
|
|
|
+ output.seek(offset - 8)
|
|
|
+ output.write(struct.pack("q", bundle_header_offset))
|
|
|
+
|
|
|
+ return total_size - new_bundle_base_offset
|
|
|
+
|
|
|
+
|
|
|
+input_directory: Path = Path(args.input_directory)
|
|
|
+content_directory: Path = Path(os.path.join(args.input_directory, "Contents"))
|
|
|
+executable_path: Path = Path(os.path.join(content_directory, args.executable_sub_path))
|
|
|
+
|
|
|
+
|
|
|
+def get_path_related_to_other_path(a: Path, b: Path) -> str:
|
|
|
+ temp = b
|
|
|
+
|
|
|
+ parts = []
|
|
|
+
|
|
|
+ while temp != a:
|
|
|
+ temp = temp.parent
|
|
|
+ parts.append(temp.name)
|
|
|
+
|
|
|
+ parts.remove(parts[-1])
|
|
|
+ parts.reverse()
|
|
|
+
|
|
|
+ return "/".join(parts)
|
|
|
+
|
|
|
+
|
|
|
+def get_path_related_to_target_exec(input_directory: Path, path: Path):
|
|
|
+ return "@executable_path/../" + get_path_related_to_other_path(
|
|
|
+ input_directory, path
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+search_path = [
|
|
|
+ Path(os.path.join(content_directory, "Frameworks")),
|
|
|
+ Path(os.path.join(content_directory, "Resources/lib")),
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+for path in content_directory.rglob("**/*.dylib"):
|
|
|
+ current_search_path = [path.parent]
|
|
|
+ current_search_path.extend(search_path)
|
|
|
+
|
|
|
+ fixup_dylib(
|
|
|
+ path,
|
|
|
+ get_path_related_to_target_exec(content_directory, path),
|
|
|
+ current_search_path,
|
|
|
+ content_directory,
|
|
|
+ )
|
|
|
+
|
|
|
+for path in content_directory.rglob("**/*.so"):
|
|
|
+ current_search_path = [path.parent]
|
|
|
+ current_search_path.extend(search_path)
|
|
|
+
|
|
|
+ fixup_dylib(
|
|
|
+ path,
|
|
|
+ get_path_related_to_target_exec(content_directory, path),
|
|
|
+ current_search_path,
|
|
|
+ content_directory,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+with open(executable_path, "rb") as input:
|
|
|
+ file_data = input.read()
|
|
|
+
|
|
|
+
|
|
|
+(bundle_base_offset, bundle_header_offset, bundle) = get_dotnet_bundle_data(file_data)
|
|
|
+
|
|
|
+add_dylib_rpath(executable_path, "@executable_path/../Frameworks/")
|
|
|
+
|
|
|
+# Recent "vanilla" version of LLVM (LLVM 13 and upper) seems to really dislike how .NET package its assemblies.
|
|
|
+# As a result, after execution of install_name_tool it will have "fixed" the symtab resulting in a missing .NET bundle...
|
|
|
+# To mitigate that, we check if the bundle offset inside the binary is valid after install_name_tool and readd .NET bundle if not.
|
|
|
+output_file_size = os.stat(executable_path).st_size
|
|
|
+if output_file_size < bundle_header_offset:
|
|
|
+ print("LLVM broke the .NET bundle, readding bundle data...")
|
|
|
+ with open(executable_path, "r+b") as output:
|
|
|
+ file_data = output.read()
|
|
|
+ bundle_data_size = write_bundle_data(
|
|
|
+ output, bundle_base_offset, output_file_size, bundle
|
|
|
+ )
|
|
|
+
|
|
|
+ # Now patch the __LINKEDIT section
|
|
|
+ new_size = output_file_size + bundle_data_size
|
|
|
+ fixup_linkedit(output, file_data, new_size)
|