#!/usr/bin/env bash set -e # Auto-detect THEOS if not set if [ -z "$THEOS" ]; then if [ -d "$HOME/theos" ]; then export THEOS="$HOME/theos" else echo -e '\033[1m\033[0;31mTHEOS not set and ~/theos not found.\nSet THEOS or install Theos to ~/theos\033[0m' exit 1 fi fi CMAKE_OSX_ARCHITECTURES="arm64e;arm64" CMAKE_OSX_SYSROOT="iphoneos" # Copy Localization resources (*.lproj) into a RyukGram.bundle. # Arg 1: destination bundle directory (created if missing). copy_localization_into_bundle() { local DEST="$1" local SRC="src/Localization/Resources" [ -d "$SRC" ] || return 0 mkdir -p "$DEST" for lproj in "$SRC"/*.lproj; do [ -d "$lproj" ] || continue cp -R "$lproj" "$DEST/" done } # Copy generic static assets (PNGs, etc.) into a RyukGram.bundle. Used for # bundled images the tweak loads via SCILocalizationBundle(). # Arg 1: destination bundle directory (created if missing). copy_bundle_assets() { local DEST="$1" local SRC="src/BundleAssets" [ -d "$SRC" ] || return 0 mkdir -p "$DEST" find "$SRC" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.pdf' \) \ -exec cp {} "$DEST/" \; } # Collect all FFmpegKit frameworks for injection ffmpegkit_frameworks() { local fws="" if [ -d "modules/ffmpegkit/ffmpegkit.framework" ]; then for fw in modules/ffmpegkit/*.framework; do fws="$fws $fw" done fi echo "$fws" } # Inject RyukGram.bundle into a .deb: # - Always: localization lproj resources. # - Optional: FFmpegKit frameworks (renamed *_sci to avoid collisions). # Path: Library/Application Support/RyukGram.bundle/ — jailbreak dlopens by full # path, Feather copies .bundle without injecting load commands for sideload. # Arg 1: path to .deb (cwd must be packages/) inject_bundle_into_deb() { local BASE_DEB="$1" local TMPDIR=$(mktemp -d) dpkg-deb -R "$BASE_DEB" "$TMPDIR" local DYLIB_DIR=$(find "$TMPDIR" -name "RyukGram.dylib" -exec dirname {} \; | head -1) [ -n "$DYLIB_DIR" ] || { rm -rf "$TMPDIR"; return; } local PREFIX="" [[ "$DYLIB_DIR" == *"/var/jb/"* ]] && PREFIX="var/jb/" local BUNDLE_DIR="$TMPDIR/${PREFIX}Library/Application Support/RyukGram.bundle" mkdir -p "$BUNDLE_DIR" ( cd .. && copy_localization_into_bundle "$BUNDLE_DIR" && copy_bundle_assets "$BUNDLE_DIR" ) if [ -d "../modules/ffmpegkit/ffmpegkit.framework" ]; then for fw in ../modules/ffmpegkit/*.framework; do cp -R "$fw" "$BUNDLE_DIR/" done local LIBS="libavutil libavcodec libavformat libavfilter libavdevice libswresample libswscale" for lib in $LIBS; do mv "$BUNDLE_DIR/${lib}.framework" "$BUNDLE_DIR/${lib}_sci.framework" install_name_tool -id "@rpath/${lib}_sci.framework/${lib}" \ "$BUNDLE_DIR/${lib}_sci.framework/${lib}" done for target in "$BUNDLE_DIR/ffmpegkit.framework/ffmpegkit" \ "$BUNDLE_DIR"/libav*_sci.framework/libav* \ "$BUNDLE_DIR"/libsw*_sci.framework/libsw*; do [ -f "$target" ] || continue for lib in $LIBS; do install_name_tool -change \ "@rpath/${lib}.framework/${lib}" \ "@rpath/${lib}_sci.framework/${lib}" \ "$target" 2>/dev/null || true done done install_name_tool -add_rpath @loader_path/.. \ "$BUNDLE_DIR/ffmpegkit.framework/ffmpegkit" 2>/dev/null || true fi dpkg-deb -b "$TMPDIR" "$BASE_DEB" rm -rf "$TMPDIR" } # Build zxPluginsInject.dylib -> packages/zxPluginsInject.dylib build_zxpi_dylib() { local MOD_DIR="modules/zxPluginsInject" local DYLIB_OUT="$MOD_DIR/.theos/obj/zxPluginsInject.dylib" if [ -z "${THEOS:-}" ]; then if [ -d "$HOME/theos" ]; then export THEOS="$HOME/theos" else echo -e '\033[1m\033[0;31mTHEOS not set and ~/theos not found\033[0m' >&2 exit 1 fi fi ( cd "$MOD_DIR" && make FINALPACKAGE=1 >/dev/null ) [ -f "$DYLIB_OUT" ] || { echo -e '\033[1m\033[0;31mzxPluginsInject.dylib build failed\033[0m' >&2 exit 1 } mkdir -p packages cp "$DYLIB_OUT" packages/zxPluginsInject.dylib # Match the @rpath LC that ipapatch writes into target binaries. install_name_tool -id "@rpath/zxPluginsInject.dylib" \ packages/zxPluginsInject.dylib 2>/dev/null || true } # LC-inject zxPluginsInject.dylib into main exec + every .appex in the IPA. # Arg 1: path to the IPA run_ipapatch() { local IPA="$1" if ! command -v ipapatch &> /dev/null; then echo -e '\033[1m\033[0;31mipapatch not found. Install it from:\033[0m' echo ' https://github.com/asdfzxcvbn/ipapatch/releases/latest' exit 1 fi echo -e '\033[1m\033[32mRunning ipapatch (zxPluginsInject LC injection)\033[0m' ipapatch --input "$IPA" --inplace --noconfirm --dylib packages/zxPluginsInject.dylib } # Build just the dylib (for Feather/manual injection) if [ "$1" == "dylib" ]; then # --fast: incremental build (no clean) if [ "$2" != "--fast" ]; then make clean 2>/dev/null || true rm -rf .theos fi echo -e '\033[1m\033[32mBuilding RyukGram dylib\033[0m' make mkdir -p packages cp .theos/obj/debug/RyukGram.dylib packages/RyukGram.dylib # Ship localization bundle next to the dylib so Feather/manual installs work. copy_localization_into_bundle "packages/RyukGram.bundle" copy_bundle_assets "packages/RyukGram.bundle" echo -e "\033[1m\033[32mDone!\033[0m\n\nDylib at: $(pwd)/packages/RyukGram.dylib\nBundle at: $(pwd)/packages/RyukGram.bundle" # Build sideloaded IPA elif [ "$1" == "sideload" ]; then # Check for FLEXing submodule HAS_FLEX=1 if [ -z "$(ls -A modules/FLEXing 2>/dev/null)" ]; then echo -e '\033[1m\033[0;33mFLEXing submodule not found — building without FLEX debugger.\033[0m' echo -e '\033[0;33mTo include FLEX, run: git submodule update --init --recursive\033[0m' echo HAS_FLEX=0 fi # Check if building with dev mode if [ "$2" == "--dev" ]; then if [ "$HAS_FLEX" == "0" ]; then echo -e '\033[1m\033[0;31mDev mode requires FLEXing submodule.\033[0m' exit 1 fi # Cache pre-built FLEX libs mkdir -p "packages/cache" cp -f ".theos/obj/debug/FLEXing.dylib" "packages/cache/FLEXing.dylib" 2>/dev/null || true cp -f ".theos/obj/debug/libflex.dylib" "packages/cache/libflex.dylib" 2>/dev/null || true if [[ ! -f "packages/cache/FLEXing.dylib" || ! -f "packages/cache/libflex.dylib" ]]; then echo -e '\033[1m\033[0;33mCould not find cached pre-built FLEX libs, building prerequisite binaries\033[0m' echo ./build.sh sideload --buildonly ./build-dev.sh true exit fi MAKEARGS='DEV=1' FLEXPATH='packages/cache/FLEXing.dylib packages/cache/libflex.dylib' COMPRESSION=0 else # Clear cached FLEX libs rm -rf "packages/cache" if [ "$HAS_FLEX" == "1" ]; then MAKEARGS='SIDELOAD=1' FLEXPATH='.theos/obj/debug/FLEXing.dylib .theos/obj/debug/libflex.dylib' else MAKEARGS='' FLEXPATH='' fi COMPRESSION=9 fi # Clean build artifacts make clean 2>/dev/null || true rm -rf .theos # Check for decrypted Instagram IPA mkdir -p packages ipaFile="$(find ./packages/ -maxdepth 1 -type f \( -iname '*com.burbn.instagram*.ipa' -o -iname 'Instagram*.ipa' -o -iname '[0-9]*.ipa' \) ! -iname 'RyukGram*.ipa' -exec basename {} \; 2>/dev/null | head -1)" if [ -z "${ipaFile}" ]; then # Auto-move any Instagram IPA from cwd into packages/ cwdIpa="$(find . -maxdepth 1 -type f \( -iname '*com.burbn.instagram*.ipa' -o -iname 'Instagram*.ipa' -o -iname '[0-9]*.ipa' \) 2>/dev/null | head -1)" if [ -n "$cwdIpa" ]; then echo -e "\033[1m\033[32mMoving $(basename "$cwdIpa") → packages/\033[0m" mv "$cwdIpa" packages/ ipaFile="$(basename "$cwdIpa")" fi fi if [ -z "${ipaFile}" ]; then echo -e '\033[1m\033[0;31mDecrypted Instagram IPA not found.\nPlace a *com.burbn.instagram*.ipa in ./ or ./packages/.\033[0m' exit 1 fi # Check for cyan and ipapatch before building (skip check for --buildonly) if [ "$2" != "--buildonly" ]; then if ! command -v cyan &> /dev/null; then echo -e '\033[1m\033[0;31mcyan not found. Install it with:\033[0m' echo ' pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip' echo echo -e '\033[0;33mUse ./build.sh sideload --buildonly to just compile without creating the IPA.\033[0m' echo -e '\033[0;33mOr use ./build.sh dylib to build the dylib for Feather injection.\033[0m' exit 1 fi if ! command -v ipapatch &> /dev/null; then echo -e '\033[1m\033[0;31mipapatch not found. Install it from:\033[0m' echo ' https://github.com/asdfzxcvbn/ipapatch/releases/latest' exit 1 fi fi echo -e '\033[1m\033[32mBuilding RyukGram tweak for sideloading (as IPA)\033[0m' make $MAKEARGS # Build zxPluginsInject.dylib so ipapatch can inject it after cyan echo -e '\033[1m\033[32mBuilding zxPluginsInject.dylib\033[0m' build_zxpi_dylib # Copy dylib to packages mkdir -p packages cp .theos/obj/debug/RyukGram.dylib packages/RyukGram.dylib # Only build libs (for future use in dev build mode) if [ "$2" == "--buildonly" ]; then exit fi # Build RyukGram.bundle with renamed frameworks for cyan injection BUNDLE_PATH="packages/RyukGram.bundle" rm -rf "$BUNDLE_PATH" mkdir -p "$BUNDLE_PATH" copy_localization_into_bundle "$BUNDLE_PATH" copy_bundle_assets "$BUNDLE_PATH" if [ -d "modules/ffmpegkit/ffmpegkit.framework" ]; then echo -e '\033[1m\033[32mBuilding RyukGram.bundle\033[0m' for fw in modules/ffmpegkit/*.framework; do cp -R "$fw" "$BUNDLE_PATH/" done LIBS="libavutil libavcodec libavformat libavfilter libavdevice libswresample libswscale" for lib in $LIBS; do mv "$BUNDLE_PATH/${lib}.framework" "$BUNDLE_PATH/${lib}_sci.framework" install_name_tool -id "@rpath/${lib}_sci.framework/${lib}" \ "$BUNDLE_PATH/${lib}_sci.framework/${lib}" done for target in "$BUNDLE_PATH/ffmpegkit.framework/ffmpegkit" \ "$BUNDLE_PATH"/libav*_sci.framework/libav* \ "$BUNDLE_PATH"/libsw*_sci.framework/libsw*; do [ -f "$target" ] || continue for lib in $LIBS; do install_name_tool -change \ "@rpath/${lib}.framework/${lib}" \ "@rpath/${lib}_sci.framework/${lib}" \ "$target" 2>/dev/null || true done done install_name_tool -add_rpath @loader_path/.. \ "$BUNDLE_PATH/ffmpegkit.framework/ffmpegkit" 2>/dev/null || true fi TWEAKPATH=".theos/obj/debug/RyukGram.dylib" if [ "$2" == "--devquick" ]; then TWEAKPATH=""; fi BUNDLE_ARG="" [ -d "$BUNDLE_PATH" ] && BUNDLE_ARG="$BUNDLE_PATH" # Create IPA: cyan injects dylib + copies RyukGram.bundle to app root echo -e '\033[1m\033[32mCreating the IPA file...\033[0m' rm -f packages/RyukGram-sideloaded.ipa cyan -i "packages/${ipaFile}" -o packages/RyukGram-sideloaded.ipa -f $TWEAKPATH $FLEXPATH $BUNDLE_ARG -c $COMPRESSION -m 15.0 -du # Inject Safari "Open in Instagram" extension into Payload/*.app/PlugIns/ # before ipapatch re-signs, so instagram.com links open the app. APPEX_SRC="extensions/OpenInstagramSafariExtension.appex" if [ -d "$APPEX_SRC" ]; then echo -e '\033[1m\033[32mEmbedding Safari extension\033[0m' INJECT_TMP=$(mktemp -d) unzip -q packages/RyukGram-sideloaded.ipa -d "$INJECT_TMP" APP_DIR="$(find "$INJECT_TMP/Payload" -maxdepth 1 -type d -name '*.app' | head -1)" if [ -n "$APP_DIR" ]; then mkdir -p "$APP_DIR/PlugIns" rm -rf "$APP_DIR/PlugIns/OpenInstagramSafariExtension.appex" cp -R "$APPEX_SRC" "$APP_DIR/PlugIns/" ( cd "$INJECT_TMP" && zip -qr -${COMPRESSION} ../repacked.ipa Payload ) mv "$INJECT_TMP/../repacked.ipa" packages/RyukGram-sideloaded.ipa fi rm -rf "$INJECT_TMP" fi run_ipapatch packages/RyukGram-sideloaded.ipa echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the ipa file at: $(pwd)/packages" # Build rootless .deb with FFmpegKit elif [ "$1" == "rootless" ]; then make clean 2>/dev/null || true rm -rf .theos echo -e '\033[1m\033[32mBuilding RyukGram tweak for rootless\033[0m' export THEOS_PACKAGE_SCHEME=rootless make package echo -e '\033[1m\033[32mInjecting RyukGram.bundle (localization + FFmpegKit) into deb\033[0m' cd packages BASE_DEB="$(ls -t *.deb | head -n1)" if [ -n "$BASE_DEB" ]; then inject_bundle_into_deb "$BASE_DEB" NEW_NAME="${BASE_DEB%.deb}-rootless.deb" mv "$BASE_DEB" "$NEW_NAME" fi cd .. [ -d "modules/ffmpegkit/ffmpegkit.framework" ] || echo -e '\033[0;33mFFmpegKit not found — deb built without FFmpegKit.\033[0m' echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" # Build rootful .deb with FFmpegKit elif [ "$1" == "rootful" ]; then make clean 2>/dev/null || true rm -rf .theos echo -e '\033[1m\033[32mBuilding RyukGram tweak for rootful\033[0m' unset THEOS_PACKAGE_SCHEME make package echo -e '\033[1m\033[32mInjecting RyukGram.bundle (localization + FFmpegKit) into deb\033[0m' cd packages BASE_DEB="$(ls -t *.deb | head -n1)" if [ -n "$BASE_DEB" ]; then inject_bundle_into_deb "$BASE_DEB" NEW_NAME="${BASE_DEB%.deb}-rootful.deb" mv "$BASE_DEB" "$NEW_NAME" fi cd .. [ -d "modules/ffmpegkit/ffmpegkit.framework" ] || echo -e '\033[0;33mFFmpegKit not found — deb built without FFmpegKit.\033[0m' echo -e "\033[1m\033[32mDone, enjoy RyukGram!\033[0m\n\nYou can find the deb file at: $(pwd)/packages" # TrollStore build — .tipa is a renamed .ipa. Skip sideload re-sign; TS signs on-device. elif [ "$1" == "trollstore" ]; then HAS_FLEX=1 if [ -z "$(ls -A modules/FLEXing 2>/dev/null)" ]; then HAS_FLEX=0 fi if [ "$HAS_FLEX" == "1" ]; then MAKEARGS='SIDELOAD=1' FLEXPATH='.theos/obj/debug/FLEXing.dylib .theos/obj/debug/libflex.dylib' else MAKEARGS='' FLEXPATH='' fi COMPRESSION=9 make clean 2>/dev/null || true rm -rf .theos mkdir -p packages ipaFile="$(find ./packages/ -maxdepth 1 -type f \( -iname '*com.burbn.instagram*.ipa' -o -iname 'Instagram*.ipa' -o -iname '[0-9]*.ipa' \) ! -iname 'RyukGram*.ipa' -exec basename {} \; 2>/dev/null | head -1)" if [ -z "${ipaFile}" ]; then cwdIpa="$(find . -maxdepth 1 -type f \( -iname '*com.burbn.instagram*.ipa' -o -iname 'Instagram*.ipa' -o -iname '[0-9]*.ipa' \) 2>/dev/null | head -1)" if [ -n "$cwdIpa" ]; then mv "$cwdIpa" packages/ ipaFile="$(basename "$cwdIpa")" fi fi if [ -z "${ipaFile}" ]; then echo -e '\033[1m\033[0;31mDecrypted Instagram IPA not found.\033[0m' exit 1 fi if ! command -v cyan &> /dev/null; then echo -e '\033[1m\033[0;31mcyan not found. Install it with:\033[0m' echo ' pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip' exit 1 fi if ! command -v ipapatch &> /dev/null; then echo -e '\033[1m\033[0;31mipapatch not found. Install it from:\033[0m' echo ' https://github.com/asdfzxcvbn/ipapatch/releases/latest' exit 1 fi echo -e '\033[1m\033[32mBuilding RyukGram tweak for TrollStore (.tipa)\033[0m' make $MAKEARGS cp .theos/obj/debug/RyukGram.dylib packages/RyukGram.dylib echo -e '\033[1m\033[32mBuilding zxPluginsInject.dylib\033[0m' build_zxpi_dylib BUNDLE_PATH="packages/RyukGram.bundle" rm -rf "$BUNDLE_PATH" mkdir -p "$BUNDLE_PATH" copy_localization_into_bundle "$BUNDLE_PATH" copy_bundle_assets "$BUNDLE_PATH" if [ -d "modules/ffmpegkit/ffmpegkit.framework" ]; then for fw in modules/ffmpegkit/*.framework; do cp -R "$fw" "$BUNDLE_PATH/" done LIBS="libavutil libavcodec libavformat libavfilter libavdevice libswresample libswscale" for lib in $LIBS; do mv "$BUNDLE_PATH/${lib}.framework" "$BUNDLE_PATH/${lib}_sci.framework" install_name_tool -id "@rpath/${lib}_sci.framework/${lib}" \ "$BUNDLE_PATH/${lib}_sci.framework/${lib}" done for target in "$BUNDLE_PATH/ffmpegkit.framework/ffmpegkit" \ "$BUNDLE_PATH"/libav*_sci.framework/libav* \ "$BUNDLE_PATH"/libsw*_sci.framework/libsw*; do [ -f "$target" ] || continue for lib in $LIBS; do install_name_tool -change \ "@rpath/${lib}.framework/${lib}" \ "@rpath/${lib}_sci.framework/${lib}" \ "$target" 2>/dev/null || true done done install_name_tool -add_rpath @loader_path/.. \ "$BUNDLE_PATH/ffmpegkit.framework/ffmpegkit" 2>/dev/null || true fi TWEAKPATH=".theos/obj/debug/RyukGram.dylib" BUNDLE_ARG="" [ -d "$BUNDLE_PATH" ] && BUNDLE_ARG="$BUNDLE_PATH" echo -e '\033[1m\033[32mCreating the TIPA file...\033[0m' rm -f packages/RyukGram-trollstore.tipa packages/RyukGram-trollstore.ipa cyan -i "packages/${ipaFile}" -o packages/RyukGram-trollstore.ipa -f $TWEAKPATH $FLEXPATH $BUNDLE_ARG -c $COMPRESSION -m 15.0 -du # Embed Safari extension. APPEX_SRC="extensions/OpenInstagramSafariExtension.appex" if [ -d "$APPEX_SRC" ]; then echo -e '\033[1m\033[32mEmbedding Safari extension\033[0m' INJECT_TMP=$(mktemp -d) unzip -q packages/RyukGram-trollstore.ipa -d "$INJECT_TMP" APP_DIR="$(find "$INJECT_TMP/Payload" -maxdepth 1 -type d -name '*.app' | head -1)" if [ -n "$APP_DIR" ]; then mkdir -p "$APP_DIR/PlugIns" rm -rf "$APP_DIR/PlugIns/OpenInstagramSafariExtension.appex" cp -R "$APPEX_SRC" "$APP_DIR/PlugIns/" ( cd "$INJECT_TMP" && zip -qr -${COMPRESSION} ../repacked.ipa Payload ) mv "$INJECT_TMP/../repacked.ipa" packages/RyukGram-trollstore.ipa fi rm -rf "$INJECT_TMP" fi run_ipapatch packages/RyukGram-trollstore.ipa mv packages/RyukGram-trollstore.ipa packages/RyukGram-trollstore.tipa echo -e "\033[1m\033[32mDone!\033[0m\n\nTIPA at: $(pwd)/packages/RyukGram-trollstore.tipa" else echo '+----------------------+' echo '|RyukGram Build Script |' echo '+----------------------+' echo echo 'Usage: ./build.sh ' echo echo ' dylib - Build the dylib only (for Feather/manual injection)' echo ' sideload - Build a patched IPA (requires cyan + decrypted IPA)' echo ' trollstore - Build a .tipa for TrollStore (requires cyan + decrypted IPA)' echo ' rootless - Build a rootless .deb package (with FFmpegKit)' echo ' rootful - Build a rootful .deb package (with FFmpegKit)' exit 1 fi