Last active
February 19, 2026 02:04
-
-
Save looselyrigorous/201468b712696d24edc5287aa4e7d100 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # install-shaders.sh — Download and install ReShade shaders | |
| # | |
| # Usage: | |
| # ./install-shaders.sh [--pick] | |
| # | |
| # Flags: | |
| # | |
| # --pick | |
| # Force interactive fzf picker mode (overrides auto-detection). | |
| # Required packages (e.g. "Standard effects") are always installed | |
| # and are not shown in the picker. Requires fzf. | |
| # | |
| # Environment variables: | |
| # | |
| # INTERACTIVE | |
| # Set to 1 to force interactive mode, 0 to disable. | |
| # Default: auto-detect (enabled if fzf is installed and INCLUDE_REPOS is not set) | |
| # | |
| # INCLUDE_REPOS | |
| # Comma-separated list of package names to include (case-insensitive substring match). | |
| # If set, disables interactive mode and uses the specified filter instead. | |
| # If unset and fzf is not available, ONLY required packages are installed. | |
| # Example: INCLUDE_REPOS="SweetFX,qUINT,iMMERSE" ./install-shaders.sh | |
| # | |
| # MAIN_PATH | |
| # Override the base reshade data path. | |
| # Default: $XDG_DATA_HOME/reshade (typically ~/.local/share/reshade) | |
| # | |
| # UPDATE_SHADERS | |
| # Set to 0 to skip running "git pull" on already-cloned repos. Default: 1 | |
| # | |
| # DRY_RUN | |
| # Set to 1 to preview actions without making changes. Default: 0 | |
| # | |
| # REBUILD_MERGE | |
| # Set to 0 to skip clearing the Merged/ directory before rebuilding. | |
| # Default: 1 (always rebuild from scratch to remove stale links) | |
| set -euo pipefail | |
| # --------------------------------------------------------------------------- | |
| # Configuration | |
| # --------------------------------------------------------------------------- | |
| # Parse command-line flags first | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --pick) INTERACTIVE=1 ;; | |
| *) | |
| echo "Unknown option: $arg" >&2 | |
| echo "Usage: $0 [--pick]" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| # Auto-detect interactive mode if not explicitly set | |
| if [[ -z "${INTERACTIVE:-}" ]]; then | |
| # Enable interactive mode if fzf is available AND INCLUDE_REPOS is not set | |
| if command -v fzf &>/dev/null && [[ -z "${INCLUDE_REPOS:-}" ]]; then | |
| INTERACTIVE=1 | |
| else | |
| INTERACTIVE=0 | |
| fi | |
| else | |
| INTERACTIVE="${INTERACTIVE}" | |
| fi | |
| XDG_DATA_HOME="${XDG_DATA_HOME:-"$HOME/.local/share"}" | |
| MAIN_PATH="${MAIN_PATH:-"$XDG_DATA_HOME/reshade"}" | |
| MERGED_DIR="$MAIN_PATH/ReShade_shaders/Merged" | |
| UPDATE_SHADERS="${UPDATE_SHADERS:-1}" | |
| DRY_RUN="${DRY_RUN:-0}" | |
| REBUILD_MERGE="${REBUILD_MERGE:-1}" | |
| PACKAGES_URL="https://raw.githubusercontent.com/crosire/reshade-shaders/refs/heads/list/EffectPackages.ini" | |
| SEPARATOR="───────────────────────────────────────────────────────────────────────────" | |
| # --------------------------------------------------------------------------- | |
| # Dependency check | |
| # --------------------------------------------------------------------------- | |
| for cmd in curl git; do | |
| if ! command -v "$cmd" &>/dev/null; then | |
| echo "Error: Required program '$cmd' is not installed." >&2 | |
| exit 1 | |
| fi | |
| done | |
| if [[ "$INTERACTIVE" -eq 1 ]] && ! command -v fzf &>/dev/null; then | |
| echo "Error: Interactive mode requires 'fzf' but it is not installed." >&2 | |
| echo "Install fzf or set INTERACTIVE=0 to disable interactive mode." >&2 | |
| exit 1 | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Include filter | |
| # --------------------------------------------------------------------------- | |
| declare -a INCLUDES=() | |
| if [[ -n "${INCLUDE_REPOS:-}" ]]; then | |
| IFS=',' read -ra INCLUDES <<< "$(echo "$INCLUDE_REPOS" | tr '[:upper:]' '[:lower:]')" | |
| for i in "${!INCLUDES[@]}"; do | |
| INCLUDES[$i]="$(echo "${INCLUDES[$i]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| done | |
| fi | |
| # In interactive mode, this array is populated by the fzf picker. | |
| declare -A INTERACTIVE_SELECTED=() | |
| # Populated after parsing; required packages are always installed. | |
| declare -A REQUIRED_NAMES=() | |
| is_included() { | |
| local name="$1" | |
| # Always include required packages (matches ReShade installer's Required=1) | |
| if [[ -n "${REQUIRED_NAMES[$name]+x}" ]]; then | |
| return 0 | |
| fi | |
| # Interactive mode: check the picker selections | |
| if [[ "$INTERACTIVE" -eq 1 ]]; then | |
| [[ -n "${INTERACTIVE_SELECTED[$name]+x}" ]] | |
| return $? | |
| fi | |
| # No filter specified: only required packages | |
| if [[ ${#INCLUDES[@]} -eq 0 ]]; then | |
| return 1 | |
| fi | |
| local name_lower | |
| name_lower="$(echo "$name" | tr '[:upper:]' '[:lower:]')" | |
| for pattern in "${INCLUDES[@]}"; do | |
| if [[ "$name_lower" == *"$pattern"* ]]; then | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Comma-separated list helper (for DenyEffectFiles) | |
| # --------------------------------------------------------------------------- | |
| is_in_csv() { | |
| local needle="$1" | |
| local csv="$2" | |
| [[ -z "$csv" ]] && return 1 | |
| local IFS=',' | |
| for entry in $csv; do | |
| entry="${entry#"${entry%%[![:space:]]*}"}" # ltrim | |
| entry="${entry%"${entry##*[![:space:]]}"}" # rtrim | |
| if [[ "$needle" == "$entry" ]]; then | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Git helpers | |
| # --------------------------------------------------------------------------- | |
| # Extract git clone URL and branch from RepositoryUrl / DownloadUrl. | |
| # The ReShade installer uses DownloadUrl (a GitHub archive zip URL) which | |
| # encodes the branch. We extract that for git clone --branch. | |
| get_git_info() { | |
| local repo_url="$1" | |
| local download_url="$2" | |
| local git_url="" | |
| local branch="" | |
| # Try RepositoryUrl first (may contain /tree/<branch>) | |
| if [[ "$repo_url" =~ ^(https://github\.com/[^/]+/[^/]+)/tree/(.+)$ ]]; then | |
| git_url="${BASH_REMATCH[1]}" | |
| branch="${BASH_REMATCH[2]}" | |
| elif [[ "$repo_url" =~ ^(https://github\.com/[^/]+/[^/]+)/?$ ]]; then | |
| git_url="${BASH_REMATCH[1]}" | |
| fi | |
| # Extract branch from DownloadUrl if we don't have one yet | |
| if [[ -z "$branch" && -n "$download_url" ]]; then | |
| if [[ "$download_url" =~ /archive/refs/heads/(.+)\.zip$ ]]; then | |
| branch="${BASH_REMATCH[1]}" | |
| elif [[ "$download_url" =~ /archive/(.+)\.zip$ ]]; then | |
| branch="${BASH_REMATCH[1]}" | |
| fi | |
| fi | |
| # Fallback: extract git URL from DownloadUrl | |
| if [[ -z "$git_url" && -n "$download_url" ]]; then | |
| if [[ "$download_url" =~ (https://github\.com/[^/]+/[^/]+)/archive/ ]]; then | |
| git_url="${BASH_REMATCH[1]}" | |
| fi | |
| fi | |
| printf '%s|%s' "$git_url" "$branch" | |
| } | |
| # Produce a filesystem-safe local directory name from a git URL + branch. | |
| make_local_name() { | |
| local git_url="$1" | |
| local branch="$2" | |
| if [[ "$git_url" =~ github\.com/([^/]+)/([^/]+)$ ]]; then | |
| local owner="${BASH_REMATCH[1]}" | |
| local repo="${BASH_REMATCH[2]}" | |
| local base | |
| base="$(printf '%s' "${owner}-${repo}" | tr '[:upper:]' '[:lower:]' | tr -c '[:alnum:]-' '-' | sed 's/-*$//')" | |
| if [[ -n "$branch" && "$branch" != "master" && "$branch" != "main" ]]; then | |
| local clean_branch | |
| clean_branch="$(printf '%s' "$branch" | tr '[:upper:]' '[:lower:]' | tr -c '[:alnum:]-' '-' | sed 's/-*$//')" | |
| base="${base}-${clean_branch}" | |
| fi | |
| echo "$base" | |
| else | |
| printf '%s' "$git_url" | md5sum | cut -c1-12 | |
| fi | |
| } | |
| # --------------------------------------------------------------------------- | |
| # InstallPath parsing | |
| # | |
| # The official EffectPackages.ini uses Windows-style relative paths like: | |
| # .\reshade-shaders\Shaders\OtisFX | |
| # .\reshade-shaders\Textures\OtisFX | |
| # .\reshade-shaders\Shaders (root — no subdirectory) | |
| # .\reshade-shaders\Textures (root — no subdirectory) | |
| # | |
| # We extract the subdirectory portion AFTER "Shaders" or "Textures" so we | |
| # can place files under Merged/Shaders/<subdir>/ or Merged/Textures/<subdir>/ | |
| # --------------------------------------------------------------------------- | |
| # Given an InstallPath like ".\reshade-shaders\Shaders\OtisFX", return "OtisFX". | |
| # For root paths like ".\reshade-shaders\Shaders" or empty, return "". | |
| extract_subdir() { | |
| local install_path="$1" | |
| local kind="$2" # "Shaders" or "Textures" | |
| # Normalise: convert backslashes to forward slashes, strip leading ./ or .\ | |
| local norm | |
| norm="$(echo "$install_path" | tr '\\' '/' | sed 's|^\./||')" | |
| # Strip trailing slash | |
| norm="${norm%/}" | |
| # Match Shaders or Textures as a full path component (preceded by /), | |
| # NOT as a substring (e.g. "reshade-shaders" contains "shaders"). | |
| # We want: .../Shaders/OtisFX → "OtisFX" | |
| # .../Shaders → "" | |
| # .../Shaders/ → "" | |
| local lower | |
| lower="$(echo "$norm" | tr '[:upper:]' '[:lower:]')" | |
| local kind_lower | |
| kind_lower="$(echo "$kind" | tr '[:upper:]' '[:lower:]')" | |
| # Match: /shaders/subpath (as a full path component, preceded by /) | |
| if [[ "$lower" =~ /${kind_lower}/(.+)$ ]]; then | |
| local prefix_len=$(( ${#lower} - ${#BASH_REMATCH[0]} + 1 + ${#kind_lower} + 1 )) | |
| echo "${norm:$prefix_len}" | |
| # Match: /shaders at end (no trailing subpath) | |
| elif [[ "$lower" =~ /${kind_lower}$ ]]; then | |
| echo "" | |
| else | |
| echo "" | |
| fi | |
| } | |
| # --------------------------------------------------------------------------- | |
| # File linking | |
| # | |
| # Mirrors the ReShade installer's MoveFiles() — recursively copies/links all | |
| # files from source to target, preserving directory structure. | |
| # | |
| # We use symlinks instead of copies so that git pull updates propagate. | |
| # Denied files are skipped. Conflicts are resolved first-come-first-served | |
| # (matching ReShade installer behavior where the first search path wins). | |
| # --------------------------------------------------------------------------- | |
| link_tree() { | |
| local src_dir="$1" # Source directory (inside the cloned repo) | |
| local dst_dir="$2" # Destination under Merged/ | |
| local deny_list="$3" # Comma-separated filenames to deny | |
| [[ -d "$src_dir" ]] || return 0 | |
| if [[ "$DRY_RUN" -eq 0 ]]; then | |
| mkdir -p "$dst_dir" | |
| fi | |
| # Link files in this directory | |
| local file | |
| for file in "$src_dir"/*; do | |
| [[ -e "$file" ]] || continue | |
| local basename | |
| basename="$(basename "$file")" | |
| if [[ -d "$file" ]]; then | |
| # Recurse into subdirectories (mirrors MoveFiles recursive behavior) | |
| link_tree "$file" "$dst_dir/$basename" "$deny_list" | |
| continue | |
| fi | |
| # Skip .addon files (same as ReShade installer MoveFiles) | |
| case "$basename" in | |
| *.addon|*.addon32|*.addon64) continue ;; | |
| esac | |
| # Skip denied effect files | |
| if [[ -n "$deny_list" ]] && is_in_csv "$basename" "$deny_list"; then | |
| echo " ✗ denied: $basename" | |
| continue | |
| fi | |
| # Skip if target already exists (first-come-first-served) | |
| if [[ -L "$dst_dir/$basename" || -e "$dst_dir/$basename" ]]; then | |
| continue | |
| fi | |
| local real_src | |
| real_src="$(realpath "$file")" | |
| if [[ "$DRY_RUN" -eq 1 ]]; then | |
| echo " → would link: $basename" | |
| else | |
| ln -s "$real_src" "$dst_dir/$basename" | |
| fi | |
| done | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Find a directory by name (case-insensitive) inside a repo clone. | |
| # | |
| # The ReShade installer does: | |
| # tempPathEffects = Directory.EnumerateDirectories(tempPath, "Shaders", SearchOption.AllDirectories).FirstOrDefault(); | |
| # | |
| # We replicate this: find the first directory named "Shaders" (or "Textures") | |
| # anywhere inside the repo, case-insensitively. | |
| # --------------------------------------------------------------------------- | |
| find_dir_ci() { | |
| local base="$1" | |
| local name="$2" | |
| find "$base" ! -path "$base" -type d -iname "$name" -print -quit 2>/dev/null | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Process a single parsed package | |
| # --------------------------------------------------------------------------- | |
| process_package() { | |
| local name="$1" | |
| local repo_url="$2" | |
| local download_url="$3" | |
| local deny_files="$4" | |
| local install_path="$5" | |
| local texture_install_path="$6" | |
| # Skip if not in the include filter | |
| if [[ -n "$name" ]] && ! is_included "$name"; then | |
| ((skipped_count++)) || true | |
| return 0 | |
| fi | |
| # Determine git URL and branch | |
| local git_info | |
| git_info="$(get_git_info "$repo_url" "$download_url")" | |
| local git_url="${git_info%%|*}" | |
| local branch="${git_info#*|}" | |
| if [[ -z "$git_url" ]]; then | |
| echo " ⚠ Could not determine git URL for '$name', skipping." | |
| return 0 | |
| fi | |
| local local_name | |
| local_name="$(make_local_name "$git_url" "$branch")" | |
| local repo_dir="$MAIN_PATH/ReShade_shaders/$local_name" | |
| # Determine the subdirectory under Shaders/ and Textures/ from InstallPath | |
| local shaders_subdir | |
| shaders_subdir="$(extract_subdir "$install_path" "Shaders")" | |
| local textures_subdir | |
| textures_subdir="$(extract_subdir "$texture_install_path" "Textures")" | |
| echo " 📦 $name" | |
| echo " repo: $git_url (branch: ${branch:-default})" | |
| echo " shaders → Merged/Shaders/${shaders_subdir:-(root)}" | |
| echo " textures→ Merged/Textures/${textures_subdir:-(root)}" | |
| [[ -n "$deny_files" ]] && echo " deny: $deny_files" | |
| if [[ "$DRY_RUN" -eq 1 ]]; then | |
| echo " [DRY RUN] would clone/update to $repo_dir" | |
| ((installed_count++)) || true | |
| return 0 | |
| fi | |
| # Clone or update the repository | |
| if [[ -d "$repo_dir" ]]; then | |
| if [[ "$UPDATE_SHADERS" -eq 1 ]]; then | |
| echo " updating..." | |
| (cd "$repo_dir" && git pull --quiet 2>&1) || echo " ⚠ git pull failed for $git_url" | |
| else | |
| echo " already cloned, skipping update" | |
| fi | |
| else | |
| echo " cloning..." | |
| local branch_args=() | |
| if [[ -n "$branch" ]]; then | |
| branch_args=(--branch "$branch") | |
| fi | |
| if ! git clone --quiet "${branch_args[@]}" "$git_url" "$repo_dir" 2>&1; then | |
| echo " ⚠ git clone failed for $git_url" | |
| return 0 | |
| fi | |
| fi | |
| # Find the Shaders and Textures directories inside the repo (case-insensitive) | |
| # This mirrors the ReShade installer's behavior: | |
| # tempPathEffects = Directory.EnumerateDirectories(tempPath, "Shaders", ...).FirstOrDefault(); | |
| # tempPathTextures = Directory.EnumerateDirectories(tempPath, "Textures", ...).FirstOrDefault(); | |
| local repo_shaders_dir | |
| repo_shaders_dir="$(find_dir_ci "$repo_dir" "Shaders")" | |
| local repo_textures_dir | |
| repo_textures_dir="$(find_dir_ci "$repo_dir" "Textures")" | |
| # Fallback: if no Shaders dir, look for directory containing .fx files | |
| # (mirrors installer: effects.Select(x => Path.GetDirectoryName(x)).OrderBy(x => x.Length).FirstOrDefault()) | |
| if [[ -z "$repo_shaders_dir" ]]; then | |
| local first_fx | |
| first_fx="$(find "$repo_dir" -type f -iname '*.fx' -print -quit 2>/dev/null)" | |
| if [[ -n "$first_fx" ]]; then | |
| repo_shaders_dir="$(dirname "$first_fx")" | |
| fi | |
| fi | |
| # Fallback: if no Textures dir, look for directory containing images | |
| if [[ -z "$repo_textures_dir" ]]; then | |
| local first_tex | |
| first_tex="$(find "$repo_dir" -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) -print -quit 2>/dev/null)" | |
| if [[ -n "$first_tex" ]]; then | |
| repo_textures_dir="$(dirname "$first_tex")" | |
| fi | |
| fi | |
| # Build target paths under Merged/ | |
| local target_shaders="$MERGED_DIR/Shaders" | |
| [[ -n "$shaders_subdir" ]] && target_shaders="$MERGED_DIR/Shaders/$shaders_subdir" | |
| local target_textures="$MERGED_DIR/Textures" | |
| [[ -n "$textures_subdir" ]] && target_textures="$MERGED_DIR/Textures/$textures_subdir" | |
| # Link shader files | |
| if [[ -n "$repo_shaders_dir" && -d "$repo_shaders_dir" ]]; then | |
| link_tree "$repo_shaders_dir" "$target_shaders" "$deny_files" | |
| fi | |
| # Link texture files | |
| if [[ -n "$repo_textures_dir" && -d "$repo_textures_dir" ]]; then | |
| link_tree "$repo_textures_dir" "$target_textures" "" | |
| fi | |
| ((installed_count++)) || true | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Main | |
| # --------------------------------------------------------------------------- | |
| echo "╔══════════════════════════════════════════════════════════════════════════╗" | |
| echo "║ ReShade Shader Installer (For use with reshade-linux.sh) ║" | |
| echo "╚══════════════════════════════════════════════════════════════════════════╝" | |
| echo "" | |
| echo " Base path: $MAIN_PATH" | |
| echo " Dry run: $( [[ $DRY_RUN -eq 1 ]] && echo "yes" || echo "no" )" | |
| echo " Update: $( [[ $UPDATE_SHADERS -eq 1 ]] && echo "yes" || echo "no" )" | |
| echo " Rebuild: $( [[ $REBUILD_MERGE -eq 1 ]] && echo "yes" || echo "no" )" | |
| echo " Picker: $( [[ $INTERACTIVE -eq 1 ]] && echo "yes" || echo "no" )" | |
| if [[ "$INTERACTIVE" -eq 0 && ${#INCLUDES[@]} -gt 0 ]]; then | |
| echo " Filter: ${INCLUDES[*]}" | |
| fi | |
| echo "" | |
| # Download EffectPackages.ini | |
| echo "Downloading EffectPackages.ini ..." | |
| PACKAGES_INI="$(curl -sL "$PACKAGES_URL")" | |
| if [[ -z "$PACKAGES_INI" ]]; then | |
| echo "Error: Failed to download EffectPackages.ini" >&2 | |
| exit 1 | |
| fi | |
| echo " ✓ downloaded" | |
| echo "" | |
| echo "$SEPARATOR" | |
| echo " Parsing shader packages" | |
| echo "$SEPARATOR" | |
| # --------------------------------------------------------------------------- | |
| # First pass: collect all packages into arrays | |
| # --------------------------------------------------------------------------- | |
| declare -a ALL_PKG_NAMES=() | |
| declare -a ALL_PKG_REPO_URLS=() | |
| declare -a ALL_PKG_DOWNLOAD_URLS=() | |
| declare -a ALL_PKG_DENY_FILES=() | |
| declare -a ALL_PKG_INSTALL_PATHS=() | |
| declare -a ALL_PKG_TEXTURE_INSTALL_PATHS=() | |
| declare -a ALL_PKG_DESCRIPTIONS=() | |
| declare -a ALL_PKG_REQUIRED=() | |
| pkg_name="" | |
| pkg_repo_url="" | |
| pkg_download_url="" | |
| pkg_deny_files="" | |
| pkg_effect_files="" | |
| pkg_enabled="" | |
| pkg_install_path="" | |
| pkg_texture_install_path="" | |
| pkg_description="" | |
| pkg_required="" | |
| _store_package() { | |
| if [[ -n "$pkg_repo_url" || -n "$pkg_download_url" ]]; then | |
| ALL_PKG_NAMES+=("$pkg_name") | |
| ALL_PKG_REPO_URLS+=("$pkg_repo_url") | |
| ALL_PKG_DOWNLOAD_URLS+=("$pkg_download_url") | |
| ALL_PKG_DENY_FILES+=("$pkg_deny_files") | |
| ALL_PKG_INSTALL_PATHS+=("$pkg_install_path") | |
| ALL_PKG_TEXTURE_INSTALL_PATHS+=("$pkg_texture_install_path") | |
| ALL_PKG_DESCRIPTIONS+=("$pkg_description") | |
| ALL_PKG_REQUIRED+=("$pkg_required") | |
| fi | |
| } | |
| while IFS= read -r line || [[ -n "$line" ]]; do | |
| # Strip carriage return | |
| line="${line%$'\r'}" | |
| # New section — store the previously accumulated package | |
| if [[ "$line" =~ ^\[.+\]$ ]]; then | |
| _store_package | |
| # Reset state for the next package | |
| pkg_name="" | |
| pkg_repo_url="" | |
| pkg_download_url="" | |
| pkg_deny_files="" | |
| pkg_effect_files="" | |
| pkg_enabled="" | |
| pkg_install_path="" | |
| pkg_texture_install_path="" | |
| pkg_description="" | |
| pkg_required="" | |
| continue | |
| fi | |
| # Parse key=value lines | |
| if [[ "$line" =~ ^PackageName=(.+)$ ]]; then | |
| pkg_name="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^PackageDescription=(.+)$ ]]; then | |
| pkg_description="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^RepositoryUrl=(.+)$ ]]; then | |
| pkg_repo_url="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^DownloadUrl=(.+)$ ]]; then | |
| pkg_download_url="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^DenyEffectFiles=(.+)$ ]]; then | |
| pkg_deny_files="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^EffectFiles=(.+)$ ]]; then | |
| pkg_effect_files="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^Enabled=(.+)$ ]]; then | |
| pkg_enabled="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^Required=(.+)$ ]]; then | |
| pkg_required="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^InstallPath=(.+)$ ]]; then | |
| pkg_install_path="${BASH_REMATCH[1]}" | |
| elif [[ "$line" =~ ^TextureInstallPath=(.+)$ ]]; then | |
| pkg_texture_install_path="${BASH_REMATCH[1]}" | |
| fi | |
| done <<< "$PACKAGES_INI" | |
| # Store the last package (no trailing section header) | |
| _store_package | |
| # Build the REQUIRED_NAMES lookup from parsed data | |
| for i in "${!ALL_PKG_NAMES[@]}"; do | |
| if [[ "${ALL_PKG_REQUIRED[$i]}" == "1" ]]; then | |
| REQUIRED_NAMES["${ALL_PKG_NAMES[$i]}"]=1 | |
| fi | |
| done | |
| echo " ✓ found ${#ALL_PKG_NAMES[@]} packages" | |
| echo "" | |
| # --------------------------------------------------------------------------- | |
| # Interactive picker (--pick / INTERACTIVE=1) | |
| # --------------------------------------------------------------------------- | |
| if [[ "$INTERACTIVE" -eq 1 ]]; then | |
| # Build the list for fzf, excluding required packages | |
| fzf_input="" | |
| for i in "${!ALL_PKG_NAMES[@]}"; do | |
| if [[ "${ALL_PKG_REQUIRED[$i]}" == "1" ]]; then | |
| continue | |
| fi | |
| local_desc="${ALL_PKG_DESCRIPTIONS[$i]}" | |
| if [[ -n "$local_desc" ]]; then | |
| fzf_input+="${ALL_PKG_NAMES[$i]} — ${local_desc}"$'\n' | |
| else | |
| fzf_input+="${ALL_PKG_NAMES[$i]}"$'\n' | |
| fi | |
| done | |
| # Count required packages for the header | |
| required_list="" | |
| for i in "${!ALL_PKG_NAMES[@]}"; do | |
| if [[ "${ALL_PKG_REQUIRED[$i]}" == "1" ]]; then | |
| required_list+=" ✓ ${ALL_PKG_NAMES[$i]} (always installed)"$'\n' | |
| fi | |
| done | |
| header="Tab: toggle · Ctrl-A: all · Ctrl-D: none · Enter: confirm" | |
| if [[ -n "$required_list" ]]; then | |
| header="${required_list}${header}" | |
| fi | |
| selected_lines="$(printf '%s' "$fzf_input" | fzf \ | |
| --multi \ | |
| --cycle \ | |
| --header="$header" \ | |
| --prompt="Shaders> " \ | |
| --delimiter=' — ' \ | |
| --with-nth=1.. \ | |
| --bind='ctrl-a:select-all,ctrl-d:deselect-all' \ | |
| )" || { | |
| echo "" | |
| echo " Selection cancelled." | |
| exit 0 | |
| } | |
| # Parse selected names (everything before the " — " delimiter) | |
| while IFS= read -r sel_line; do | |
| [[ -z "$sel_line" ]] && continue | |
| sel_name="${sel_line%% — *}" | |
| INTERACTIVE_SELECTED["$sel_name"]=1 | |
| done <<< "$selected_lines" | |
| echo "" | |
| echo " Selected ${#INTERACTIVE_SELECTED[@]} package(s)" | |
| echo "" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Second pass: process packages | |
| # --------------------------------------------------------------------------- | |
| # Prepare Merged/ directory | |
| if [[ "$DRY_RUN" -eq 0 ]]; then | |
| if [[ "$REBUILD_MERGE" -eq 1 && -d "$MERGED_DIR" ]]; then | |
| echo "Removing old Merged/ directory ..." | |
| rm -rf "$MERGED_DIR" | |
| fi | |
| mkdir -p "$MERGED_DIR/Shaders" | |
| mkdir -p "$MERGED_DIR/Textures" | |
| fi | |
| echo "$SEPARATOR" | |
| echo " Processing shader packages" | |
| echo "$SEPARATOR" | |
| installed_count=0 | |
| skipped_count=0 | |
| for i in "${!ALL_PKG_NAMES[@]}"; do | |
| process_package \ | |
| "${ALL_PKG_NAMES[$i]}" \ | |
| "${ALL_PKG_REPO_URLS[$i]}" \ | |
| "${ALL_PKG_DOWNLOAD_URLS[$i]}" \ | |
| "${ALL_PKG_DENY_FILES[$i]}" \ | |
| "${ALL_PKG_INSTALL_PATHS[$i]}" \ | |
| "${ALL_PKG_TEXTURE_INSTALL_PATHS[$i]}" | |
| done | |
| echo "$SEPARATOR" | |
| # --------------------------------------------------------------------------- | |
| # Post-processing: symlink ReShade.fxh into Merged/ for "../ReShade.fxh" | |
| # | |
| # Several shaders (Warp-FX, AstrayFX, Depth3D, fubax, qUINT, etc.) use | |
| # #include "../ReShade.fxh" | |
| # which resolves to the parent of the Shaders/ directory. | |
| # Symlink the ReShade headers into Merged/ so these resolve. | |
| # --------------------------------------------------------------------------- | |
| if [[ "$DRY_RUN" -eq 0 ]]; then | |
| for header in ReShade.fxh ReShadeUI.fxh; do | |
| if [[ -e "$MERGED_DIR/Shaders/$header" && ! -e "$MERGED_DIR/$header" ]]; then | |
| ln -s "$(realpath "$MERGED_DIR/Shaders/$header")" "$MERGED_DIR/$header" | |
| echo " ✓ Linked $header → Merged/ (for #include \"../$header\")" | |
| fi | |
| done | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Handle External_shaders (user's custom shaders, same as reshade-linux.sh) | |
| # --------------------------------------------------------------------------- | |
| if [[ -d "$MAIN_PATH/External_shaders" && "$DRY_RUN" -eq 0 ]]; then | |
| echo "" | |
| echo "Linking External_shaders ..." | |
| for file in "$MAIN_PATH/External_shaders"/*; do | |
| [[ -f "$file" ]] || continue | |
| local_basename="$(basename "$file")" | |
| if [[ ! -L "$MERGED_DIR/Shaders/$local_basename" && ! -e "$MERGED_DIR/Shaders/$local_basename" ]]; then | |
| ln -s "$(realpath "$file")" "$MERGED_DIR/Shaders/$local_basename" | |
| echo " ✓ $local_basename" | |
| fi | |
| done | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Summary | |
| # --------------------------------------------------------------------------- | |
| echo "" | |
| echo "$SEPARATOR" | |
| echo " Done! $installed_count installed, $skipped_count skipped" | |
| echo "$SEPARATOR" | |
| echo "" | |
| echo " Shaders: $MERGED_DIR/Shaders/" | |
| echo " Textures: $MERGED_DIR/Textures/" | |
| echo "" | |
| echo " ReShade search paths should include two asterisks (**) at the end to search recursively:" | |
| echo "" | |
| echo " Settings -> Effect search paths = .\\reshade-shaders\\Shaders\\**" | |
| echo " Settings -> Texture search paths = .\\reshade-shaders\\Textures\\**" | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment