#!/usr/bin/env bash set -euo pipefail set -x SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) RUST_DIR="$SCRIPT_DIR/rust" TOOLCHAIN="${RUST_TOOLCHAIN:-}" OMVLL_VERSION="${OMVLL_VERSION:-1.4.1}" OMVLL_LINUX_ASSET="${OMVLL_LINUX_ASSET:-omvll_v1-4-1_linux_2025-10-01T09.33.59.tar.gz}" if [ ! -d "$RUST_DIR" ]; then echo "Unable to locate Rust sources under $RUST_DIR" >&2 exit 1 fi HOST_TAG="" HOST_LIB_SUBDIR="" NDK_TOOLCHAIN_DIR="" NDK_LIB_DIR="" detect_host_tag() { local os="$(uname -s | tr '[:upper:]' '[:lower:]')" local arch="$(uname -m)" case "$os" in linux*) HOST_TAG="linux-x86_64" HOST_LIB_SUBDIR="lib64" ;; darwin*) if [[ "$arch" == "arm64" ]]; then HOST_TAG="darwin-arm64" else HOST_TAG="darwin-x86_64" fi HOST_LIB_SUBDIR="lib" ;; msys*|mingw*|cygwin*) HOST_TAG="windows-x86_64" HOST_LIB_SUBDIR="lib" ;; *) echo "Unsupported host platform: $os ($arch)" >&2 exit 1 ;; esac } ensure_ndk_for_host() { local try_home="$1" local bin_dir="$try_home/toolchains/llvm/prebuilt/$HOST_TAG/bin" local lib_root="$try_home/toolchains/llvm/prebuilt/$HOST_TAG" local lib_dir="$lib_root/$HOST_LIB_SUBDIR" if [ ! -d "$lib_dir" ]; then if [ -d "$lib_root/lib64" ]; then lib_dir="$lib_root/lib64" elif [ -d "$lib_root/lib" ]; then lib_dir="$lib_root/lib" fi fi if [ -d "$bin_dir" ] && [ -d "$lib_dir" ]; then ANDROID_NDK_HOME="$try_home" NDK_TOOLCHAIN_DIR="$bin_dir" NDK_LIB_DIR="$lib_dir" echo "$bin_dir" return 0 fi return 1 } provision_linux_ndk() { local tmp_ndk="${ANDROID_NDK_TMP:-$HOME/.cache/purrfectsnap-ndk}" local extracted_dir="$tmp_ndk/android-ndk-r28b" if [ -d "$extracted_dir/toolchains/llvm/prebuilt" ]; then if ensure_ndk_for_host "$extracted_dir"; then ANDROID_NDK_HOME="$extracted_dir" return fi fi rm -rf "$tmp_ndk" mkdir -p "$tmp_ndk" curl -fsSL https://dl.google.com/android/repository/android-ndk-r28b-linux.zip -o "$tmp_ndk/ndk.zip" unzip -oq "$tmp_ndk/ndk.zip" -d "$tmp_ndk" ANDROID_NDK_HOME="$(find "$tmp_ndk" -maxdepth 1 -type d -name 'android-ndk-r*' | head -n1)" # Permissions are preserved during extraction; no additional chmod required. } ensure_omvll_bundle() { local archive_url="${OMVLL_ARCHIVE_URL:-https://github.com/open-obfuscator/o-mvll/releases/download/${OMVLL_VERSION}/${OMVLL_LINUX_ASSET}}" OMVLL_CACHE_DIR="$SCRIPT_DIR/.omvll/$OMVLL_VERSION" OMVLL_PLUGIN_PATH="$OMVLL_CACHE_DIR/omvll-ndk.so" local tmp_tar="" download_omvll_bundle() { rm -rf "$OMVLL_CACHE_DIR" mkdir -p "$OMVLL_CACHE_DIR" tmp_tar=$(mktemp) curl -fL "$archive_url" -o "$tmp_tar" tar -xzf "$tmp_tar" -C "$OMVLL_CACHE_DIR" rm -f "$tmp_tar" } if [ ! -f "$OMVLL_PLUGIN_PATH" ]; then echo "Downloading O-MVLL bundle from $archive_url" download_omvll_bundle fi if [ ! -f "$OMVLL_PLUGIN_PATH" ]; then local found_plugin found_plugin=$(find "$OMVLL_CACHE_DIR" -type f -name 'omvll-ndk.so' | head -n1 || true) if [ -n "$found_plugin" ] && [ -f "$found_plugin" ]; then OMVLL_PLUGIN_PATH="$found_plugin" else echo "Failed to locate O-MVLL plugin under $OMVLL_PLUGIN_PATH" >&2 echo "Contents:" >&2 ls -la "$OMVLL_CACHE_DIR" >&2 || true exit 1 fi fi if [ -z "${OMVLL_PYTHONPATH:-}" ]; then local py_root py_root=$(find "$OMVLL_CACHE_DIR" -maxdepth 1 -type d -name 'Python-*' | head -n1 || true) if [ -z "$py_root" ] || [ ! -d "$py_root/Lib" ]; then echo "OMVLL cache is incomplete. Re-downloading bundle..." >&2 download_omvll_bundle py_root=$(find "$OMVLL_CACHE_DIR" -maxdepth 1 -type d -name 'Python-*' | head -n1 || true) if [ -z "$py_root" ] || [ ! -d "$py_root/Lib" ]; then echo "Unable to determine Python stdlib directory inside the O-MVLL bundle" >&2 echo "OMVLL_CACHE_DIR: $OMVLL_CACHE_DIR" >&2 echo "Contents:" >&2 ls -la "$OMVLL_CACHE_DIR" >&2 || true exit 1 fi fi export OMVLL_PYTHONPATH="$py_root/Lib" fi local plugin_target="/tmp/purrfectsnap-omvll/$OMVLL_VERSION/omvll-ndk.so" mkdir -p "$(dirname "$plugin_target")" cp -f "$OMVLL_PLUGIN_PATH" "$plugin_target" OMVLL_PLUGIN_PATH="$plugin_target" echo "OMVLL plugin path: $OMVLL_PLUGIN_PATH" export OMVLL_HOME="$OMVLL_CACHE_DIR" } append_rustflags() { local var_name="$1" local current_value="${!var_name:-}" local addition="-Clink-arg=-fpass-plugin=$OMVLL_PLUGIN_PATH" if [[ "$current_value" == *"$addition"* ]]; then return fi if [ -n "$current_value" ]; then export "$var_name"="$current_value $addition" else export "$var_name"="$addition" fi } # Detect host platform early so we know which NDK prebuilt we need detect_host_tag # Default toolchain selection (favor GNU toolchain on Windows to avoid MSVC link.exe requirement) if [[ -z "$TOOLCHAIN" ]]; then if [[ "$HOST_TAG" == windows-* ]]; then TOOLCHAIN="stable-x86_64-pc-windows-gnu" else TOOLCHAIN="stable" fi fi # OMVLL is required and only supported on Linux/macOS/WSL. Fail fast on plain Windows. USE_OMVLL=true IS_WSL=false if grep -qi microsoft /proc/version 2>/dev/null; then IS_WSL=true fi if [[ "$HOST_TAG" == windows-* && "$IS_WSL" != true ]]; then echo "OMVLL requires a Linux/WSL environment. Please run the build via WSL (e.g., set BASH_PATH=C:\\Windows\\System32\\bash.exe)." >&2 exit 1 fi # Install rustup if it is not already available if ! command -v rustup >/dev/null 2>&1; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$TOOLCHAIN" fi # Add cargo to PATH when rustup installed the toolchain locally if [ -f "$HOME/.cargo/env" ]; then # shellcheck source=/dev/null source "$HOME/.cargo/env" fi # Ensure the requested toolchain (default: stable) is installed rustup toolchain install "$TOOLCHAIN" --profile minimal if [ -n "${ANDROID_NDK_HOME:-}" ] && [ -d "$ANDROID_NDK_HOME" ]; then if ensure_ndk_for_host "$ANDROID_NDK_HOME"; then : fi fi if [ -z "$NDK_TOOLCHAIN_DIR" ]; then if [[ "$HOST_TAG" != linux-* ]]; then echo "ANDROID_NDK_HOME must point to a valid NDK for $HOST_TAG" >&2 exit 1 fi echo "Provisioning Linux NDK r28b inside container..." >&2 provision_linux_ndk ensure_ndk_for_host "$ANDROID_NDK_HOME" fi if [ -z "$NDK_TOOLCHAIN_DIR" ]; then echo "ERROR: Could not find a $HOST_TAG NDK toolchain bin directory." >&2 exit 1 fi # Normalize NDK paths for msys/git-bash on Windows so PATH lookups work if [[ "$HOST_TAG" == windows-* ]]; then if command -v cygpath >/dev/null 2>&1; then NDK_TOOLCHAIN_DIR=$(cygpath -u "$NDK_TOOLCHAIN_DIR") NDK_LIB_DIR=$(cygpath -u "$NDK_LIB_DIR") else # best-effort slash normalization NDK_TOOLCHAIN_DIR=${NDK_TOOLCHAIN_DIR//\\//} NDK_LIB_DIR=${NDK_LIB_DIR//\\//} fi fi echo "Using NDK toolchain at: $NDK_TOOLCHAIN_DIR" ls -l "$NDK_TOOLCHAIN_DIR/aarch64-linux-android28-clang" || true export PATH="$NDK_TOOLCHAIN_DIR:$PATH" if [ -z "${LD_LIBRARY_PATH:-}" ]; then export LD_LIBRARY_PATH="$NDK_LIB_DIR" else export LD_LIBRARY_PATH="$NDK_LIB_DIR:$LD_LIBRARY_PATH" fi if [[ "$HOST_TAG" == darwin-* ]]; then if [ -z "${DYLD_LIBRARY_PATH:-}" ]; then export DYLD_LIBRARY_PATH="$NDK_LIB_DIR" else export DYLD_LIBRARY_PATH="$NDK_LIB_DIR:$DYLD_LIBRARY_PATH" fi fi ensure_omvll_bundle append_rustflags CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS append_rustflags CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_RUSTFLAGS if [ -z "${OMVLL_CONFIG:-}" ]; then export OMVLL_CONFIG="$SCRIPT_DIR/omvll_config.py" fi if [ -z "${OMVLL_PYTHONPATH:-}" ] || [ ! -d "$OMVLL_PYTHONPATH" ]; then echo "OMVLL_PYTHONPATH is not configured with a valid stdlib" >&2 exit 1 fi rustup target add --toolchain "$TOOLCHAIN" "$1" HOST_CC="" if command -v gcc >/dev/null 2>&1; then HOST_CC="gcc" elif command -v clang >/dev/null 2>&1; then HOST_CC="clang" elif command -v cc >/dev/null 2>&1; then HOST_CC="cc" else echo "Error: No host C compiler found. Build scripts require gcc, clang, or cc." >&2 echo "Install build-essential: sudo apt-get update && sudo apt-get install -y build-essential" >&2 exit 1 fi export CC="$HOST_CC" export HOST_CC="$HOST_CC" case "$1" in aarch64-linux-android) CLANG_BIN="$NDK_TOOLCHAIN_DIR/aarch64-linux-android28-clang" AR_BIN="$NDK_TOOLCHAIN_DIR/llvm-ar" if [[ "$HOST_TAG" == windows-* ]]; then CLANG_BIN="${CLANG_BIN}.cmd" AR_BIN="${AR_BIN}.exe" fi export CC_aarch64_linux_android="$CLANG_BIN" export AR_aarch64_linux_android="$AR_BIN" export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$CLANG_BIN" export CFLAGS_aarch64_linux_android="-DZSTD_LEGACY_SUPPORT=0 -DZSTD_DISABLE_ASM" ;; armv7-linux-androideabi) CLANG_BIN="$NDK_TOOLCHAIN_DIR/armv7a-linux-androideabi28-clang" AR_BIN="$NDK_TOOLCHAIN_DIR/llvm-ar" if [[ "$HOST_TAG" == windows-* ]]; then CLANG_BIN="${CLANG_BIN}.cmd" AR_BIN="${AR_BIN}.exe" fi export CC_armv7_linux_androideabi="$CLANG_BIN" export AR_armv7_linux_androideabi="$AR_BIN" export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$CLANG_BIN" export CFLAGS_armv7_linux_androideabi="-DZSTD_LEGACY_SUPPORT=0 -DZSTD_DISABLE_ASM -mfloat-abi=softfp -mfpu=neon" ;; *) echo "Unknown target $1" >&2 ;; esac cd "$RUST_DIR" rustup run "$TOOLCHAIN" cargo build --release --target "$1"