Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions .github/dylib_bundler.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/bin/zsh

# this is a script created for Mythic to bundle its dependencies' dylibs and copy them to the appropriate location.

set -e

# Output directory (can be overridden via environment variable)
ENGINE_DIR="${ENGINE_DIR:-Engine/wine}"
BREW_PREFIX=$(brew --prefix)

# List of GStreamer plugins to bundle, minus the 'libgst' prefix
GSTREAMER_PLUGINS=(
applemedia
asf
audioconvert
audioparsers
audioresample
avi
coreelements
debug
deinterlace
id3demux
isomp4
libav
opengl
playback
typefindfunctions
videoconvertscale
videofilter
videoparsersbad
wavparse
)

# list of keg-only formulae and their main dylib names
BUNDLE_LIBS=(
molten-vk:libMoltenVK
sdl2:libSDL2-2.0.0
freetype:libfreetype
gnutls:libgnutls
libpng:libpng16
libtiff:libtiff
libpcap:libpcap
jpeg:libjpeg
libffi:libffi
)

typeset -A seen_dylibs
typeset -a all_dylibs

get_lib_path() {
local formula="$1" libname="$2"
local prefix=$(brew --prefix "$formula" 2>/dev/null) || return 1
find "$prefix/lib" -maxdepth 1 -name "${libname}*.dylib" -type f 2>/dev/null | head -1
}

resolve_rpath() {
local name="${1#@rpath/}"

# Check main lib dir first
[[ -f "${BREW_PREFIX}/lib/${name}" ]] && { echo "${BREW_PREFIX}/lib/${name}"; return; }

# Search keg-only formula lib dirs
for entry in "${BUNDLE_LIBS[@]}"; do
local formula="${entry%%:*}"
local lib_path="$(brew --prefix "$formula" 2>/dev/null)/lib/${name}"
[[ -f "$lib_path" ]] && { echo "$lib_path"; return; }
done
}

normalize_path() {
python3 -c "import os; print(os.path.realpath('$1'))" 2>/dev/null || echo "$1"
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalize_path function has a command injection vulnerability. The path argument is directly interpolated into a Python command string without proper escaping. If a file path contains special characters like single quotes, backticks, or dollar signs, it could lead to arbitrary command execution. Consider using a safer approach such as passing the path as a JSON-escaped string or using Python's sys.argv.

Suggested change
python3 -c "import os; print(os.path.realpath('$1'))" 2>/dev/null || echo "$1"
python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' -- "$1" 2>/dev/null || echo "$1"

Copilot uses AI. Check for mistakes.
}

find_dependencies() {
local dylib="$1"
local norm=$(normalize_path "$dylib")

[[ -n "${seen_dylibs[$norm]}" ]] && return
seen_dylibs[$norm]=1
all_dylibs+=("$dylib")

local -a queue=("$dylib")

while [[ ${#queue[@]} -gt 0 ]]; do
local current="${queue[1]}"
queue=("${queue[@]:1}")

for ref in $(otool -L "$current" 2>/dev/null | tail -n +2 | awk '/^\t/ {print $1}' | grep '\.dylib'); do
[[ "$ref" == /usr/lib/* || "$ref" == /System/* ]] && continue
[[ "$ref" == @loader_path/* || "$ref" == @executable_path/* ]] && continue

[[ "$ref" == @rpath/* ]] && { ref=$(resolve_rpath "$ref"); [[ -z "$ref" ]] && continue; }

norm=$(normalize_path "$ref")
[[ -z "${seen_dylibs[$norm]}" ]] && {
seen_dylibs[$norm]=1
all_dylibs+=("$ref")
queue+=("$ref")
}
done
done
}

fix_install_names() {
local file="$1" prefix="$2"
chmod u+w "$file"
install_name_tool -id "${prefix}$(basename "$file")" "$file" 2>/dev/null || true

otool -L "$file" | grep -v "$file" | awk '{print $1}' | while read -r lib_path; do
[[ "$lib_path" != /usr/lib* && "$lib_path" != /System/* ]] && \
install_name_tool -change "$lib_path" "${prefix}${lib_path##*/}" "$file" 2>/dev/null || true
done
codesign -fs- "$file" 2>/dev/null || true
}

copy_dylib() {
local lib="$1" dest_dir="${ENGINE_DIR}/lib" prefix="@loader_path/"

[[ "$lib" == *"/gstreamer-1.0/"* ]] && { dest_dir="${ENGINE_DIR}/lib/gstreamer-1.0"; prefix="@loader_path/../"; }

local dest="${dest_dir}/$(basename "$lib")"
mkdir -p "$dest_dir"
[[ -f "$dest" ]] && return 0

cp -L "$lib" "$dest"
fix_install_names "$dest" "$prefix"
}

main() {
local gst_prefix=$(brew --prefix gstreamer)

echo "=== Processing GStreamer plugins ==="
for plugin in "${GSTREAMER_PLUGINS[@]}"; do
local lib_path="${gst_prefix}/lib/gstreamer-1.0/libgst${plugin}.dylib"
[[ -f "$lib_path" ]] && find_dependencies "$lib_path" || echo "Warning: $plugin not found"
done

echo "=== Processing libraries ==="
for entry in "${BUNDLE_LIBS[@]}"; do
local formula="${entry%%:*}" libname="${entry#*:}"
local lib_path=$(get_lib_path "$formula" "$libname")
[[ -n "$lib_path" ]] && find_dependencies "$lib_path" || echo "Warning: $formula not found"
done

echo "=== Copying ${#all_dylibs[@]} dylibs ==="
for dylib in "${all_dylibs[@]}"; do copy_dylib "$dylib"; done

[[ -d "${gst_prefix}/lib/gstreamer-1.0/include" ]] && {
mkdir -p "${ENGINE_DIR}/lib/gstreamer-1.0"
cp -a "${gst_prefix}/lib/gstreamer-1.0/include" "${ENGINE_DIR}/lib/gstreamer-1.0/"
}

echo "=== Fixing Wine .so files ==="
for so in "${ENGINE_DIR}"/lib/wine/x86_64-unix/*.so(N); do
otool -L "$so" 2>/dev/null | grep -q "/usr/local\|/opt/homebrew" && fix_install_names "$so" "@rpath/"
done

echo "=== Done: ${#all_dylibs[@]} dylibs bundled ==="
}

main "$@"
51 changes: 41 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
env:
MACOSX_DEPLOYMENT_TARGET: 10.15
SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk
TOOLCHAINS: com.applex.dt.toolchain.Xcode13
TOOLCHAINS: com.applex.dt.toolchain.Xcode13 # from gcenx

PATH: /usr/local/bin:/usr/local/opt/bison/bin:/usr/bin:/bin:/usr/sbin:/sbin

Expand Down Expand Up @@ -57,7 +57,8 @@ jobs:
sudo mkdir -p /Library/Developer/CommandLineTools/SDKs
curl -fL https://github.com/Gcenx/macos-sdk/releases/download/12.3/MacOSX12.3.tar.bz2 \
| sudo tar -xj -C /Library/Developer/CommandLineTools/SDKs


# ensure that when updating dependencies, the dylib bundler script is also updated
- name: install build & runtime dependencies
run: |
brew install bison \
Expand Down Expand Up @@ -86,7 +87,7 @@ jobs:
brew install local/mingw-w64/mingw-w64

- name: create build directory
run: mkdir -p build
run: mkdir build

- name: configure wine
working-directory: build
Expand Down Expand Up @@ -131,24 +132,49 @@ jobs:
run: make -j$(sysctl -n hw.ncpu)

- name: create install directory
run: mkdir -p install
run: mkdir install

- name: install wine
working-directory: build
run: make install-lib DESTDIR=${{ github.workspace }}/install

- name: add wine64 symlink for backward compatibility
- name: add wine64 as an executable wrapper for backward compatibility
working-directory: install/bin
run: ln -s wine wine64
run: |
cat > wine64 << 'EOF'
#!/usr/bin/env bash

# this is a wrapper created for Mythic to forward `wine64` calls to `wine`.
# for older versions of Mythic, which expected a wine64 binary to be present.

set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$DIR/wine" "$@"
EOF

chmod +x wine64

- name: install wine mono
working-directory: install/share/wine
run: |
mkdir -p mono
curl -fL https://github.com/wine-mono/wine-mono/releases/download/wine-mono-8.1.0/wine-mono-8.1.0-x86.tar.xz \
| tar -xJ -C mono

- name: install wine gecko
working-directory: install/share/wine
run: |
mkdir -p gecko
curl -fL https://dl.winehq.org/wine/wine-gecko/2.47.4/wine-gecko-2.47.4-x86.tar.xz \
| tar -xJ -C gecko

- name: create Engine directory
run: mkdir -p Engine
run: mkdir Engine

- name: copy build artifacts
run: |
# copy wine installation
mkdir -p Engine/wine
cp -R install/* Engine/wine
ditto --rsrc install Engine/wine

- name: copy externals
run: |
Expand All @@ -158,12 +184,17 @@ jobs:

# copy gptk libraries
ditto src/external/gptk-*/redist/lib/ Engine/wine/lib/

- name: bundle dylib dependencies
run: |
chmod +x src/.github/dylib_bundler.zsh
ENGINE_DIR=Engine/wine src/.github/dylib_bundler.zsh

- name: copy engine properties file
run: cp -R src/Mythic/Properties.plist Engine/

- name: compress engine artifact
run: tar -cJf Engine.tar.xz -C Engine .
run: tar -cf Engine.tar.xz -C Engine --use-compress-program="xz -9 -T0" .

- name: upload engine artifact
uses: actions/upload-artifact@v5
Expand Down
Loading