#!/bin/bash

# 批量修改 macOS 应用中所有二进制文件的构建版本信息
# 作者: GitHub Copilot
# 日期: 2025-07-06

# 默认参数
DEFAULT_PLATFORM="1"           # 1 = macOS
DEFAULT_MIN_VERSION="13.7.6"      # 最小版本
DEFAULT_SDK_VERSION="26.0"      # SDK 版本
DEFAULT_TOOL_TYPE="3"          # 3 = LD (链接器)
DEFAULT_TOOL_VERSION="1218.0"   # 工具版本

# 显示帮助信息
show_help() {
    echo "用法: $0 [选项] <应用路径>"
    echo ""
    echo "批量修改 macOS 应用中所有二进制文件的构建版本信息"
    echo ""
    echo "参数:"
    echo "  应用路径              要处理的 .app 文件路径"
    echo ""
    echo "选项:"
    echo "  -m, --min-version     最小版本 (默认: $DEFAULT_MIN_VERSION)"
    echo "  -s, --sdk-version     SDK 版本 (默认: $DEFAULT_SDK_VERSION)"
    echo "  -t, --tool-version    工具版本 (默认: $DEFAULT_TOOL_VERSION)"
    echo "  -p, --platform        平台 (默认: $DEFAULT_PLATFORM = macOS)"
    echo "  --tool-type           工具类型 (默认: $DEFAULT_TOOL_TYPE = LD)"
    echo "  -h, --help            显示此帮助信息"
    echo "  --dry-run             仅显示将要处理的文件，不实际修改"
    echo "  --remove-codesign     移除所有代码签名信息"
    echo "  --codesign-only       仅移除代码签名，不修改构建版本"
    echo ""
    echo "示例:"
    echo "  $0 /Applications/MyApp.app"
    echo "  $0 -m 13.7.6 -s 26.0 /Applications/MyApp.app"
    echo "  $0 --dry-run /Applications/MyApp.app"
    echo "  $0 --remove-codesign /Applications/MyApp.app"
    echo "  $0 --codesign-only /Applications/MyApp.app"
    echo "  $0 --modern --remove-codesign /Applications/MyApp.app"
    echo ""
    echo "预设配置:"
    echo "  --console             使用与系统 Console.app 相同的配置 (26.0/26.0)"
    echo "  --electron-legacy     使用 Electron 传统配置 (11.0/15.4)"
    echo "  --modern              使用现代配置 (13.7.6/26.0)"
}

# 解析命令行参数
PLATFORM="$DEFAULT_PLATFORM"
MIN_VERSION="$DEFAULT_MIN_VERSION"
SDK_VERSION="$DEFAULT_SDK_VERSION"
TOOL_TYPE="$DEFAULT_TOOL_TYPE"
TOOL_VERSION="$DEFAULT_TOOL_VERSION"
DRY_RUN=false
REMOVE_CODESIGN=false
CODESIGN_ONLY=false
APP_PATH=""

while [[ $# -gt 0 ]]; do
    case $1 in
        -m|--min-version)
            MIN_VERSION="$2"
            shift 2
            ;;
        -s|--sdk-version)
            SDK_VERSION="$2"
            shift 2
            ;;
        -t|--tool-version)
            TOOL_VERSION="$2"
            shift 2
            ;;
        -p|--platform)
            PLATFORM="$2"
            shift 2
            ;;
        --tool-type)
            TOOL_TYPE="$2"
            shift 2
            ;;
        --console)
            MIN_VERSION="26.0"
            SDK_VERSION="26.0"
            TOOL_VERSION="1218.0"
            shift
            ;;
        --electron-legacy)
            MIN_VERSION="11.0"
            SDK_VERSION="15.4"
            TOOL_VERSION="21.0"
            shift
            ;;
        --modern)
            MIN_VERSION="13.7.6"
            SDK_VERSION="26.0"
            TOOL_VERSION="1218.0"
            shift
            ;;
        --dry-run)
            DRY_RUN=true
            shift
            ;;
        --remove-codesign)
            REMOVE_CODESIGN=true
            shift
            ;;
        --codesign-only)
            CODESIGN_ONLY=true
            shift
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        -*)
            echo "错误: 未知选项 $1"
            show_help
            exit 1
            ;;
        *)
            if [ -z "$APP_PATH" ]; then
                APP_PATH="$1"
            else
                echo "错误: 只能指定一个应用路径"
                show_help
                exit 1
            fi
            shift
            ;;
    esac
done

# 检查是否提供了应用路径
if [ -z "$APP_PATH" ]; then
    echo "错误: 必须指定应用路径"
    show_help
    exit 1
fi

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}=== macOS 应用构建版本批量更新工具 ===${NC}"
echo -e "${YELLOW}目标应用:${NC} $(basename "$APP_PATH")"
echo -e "${YELLOW}目标配置:${NC}"
echo -e "  平台: macOS (${PLATFORM})"
echo -e "  最小版本: ${MIN_VERSION}"
echo -e "  SDK 版本: ${SDK_VERSION}"
echo -e "  工具: LD (${TOOL_TYPE}) 版本 ${TOOL_VERSION}"
if [ "$DRY_RUN" = true ]; then
    echo -e "${YELLOW}  模式: 预览模式 (--dry-run)${NC}"
fi
if [ "$REMOVE_CODESIGN" = true ]; then
    echo -e "${YELLOW}  附加: 移除代码签名信息${NC}"
fi
if [ "$CODESIGN_ONLY" = true ]; then
    echo -e "${YELLOW}  模式: 仅移除代码签名${NC}"
fi
echo ""

# 检查 vtool 是否存在
if ! command -v vtool &> /dev/null; then
    echo -e "${RED}错误: 未找到 vtool 命令${NC}"
    exit 1
fi

# 检查 codesign 是否存在（仅在需要移除代码签名时检查）
if [ "$REMOVE_CODESIGN" = true ] || [ "$CODESIGN_ONLY" = true ]; then
    if ! command -v codesign &> /dev/null; then
        echo -e "${RED}错误: 未找到 codesign 命令${NC}"
        exit 1
    fi
fi

# 检查应用是否存在
if [ ! -d "$APP_PATH" ]; then
    echo -e "${RED}错误: 未找到应用路径: $APP_PATH${NC}"
    exit 1
fi

# 检查是否是 .app 文件
if [[ ! "$APP_PATH" == *.app ]]; then
    echo -e "${RED}错误: 指定的路径不是 .app 文件: $APP_PATH${NC}"
    exit 1
fi

# 创建临时目录（仅在非预览模式下）
if [ "$DRY_RUN" = false ]; then
    TEMP_DIR=$(mktemp -d)
    echo -e "${BLUE}创建临时目录: $TEMP_DIR${NC}"
fi

# 函数: 处理单个二进制文件
process_binary() {
    local binary_path="$1"
    local binary_name=$(basename "$binary_path")
    
    echo -e "${YELLOW}处理: $binary_name${NC}"
    
    # 检查是否是 Mach-O 文件
    if ! file "$binary_path" | grep -q "Mach-O"; then
        echo -e "${YELLOW}  跳过: 不是 Mach-O 文件${NC}"
        return 0
    fi
    
    # 检查是否有构建版本信息（仅在需要修改构建版本时检查）
    local has_build_version=false
    if [ "$CODESIGN_ONLY" = false ]; then
        if vtool -show-build "$binary_path" 2>/dev/null | grep -q "LC_BUILD_VERSION"; then
            has_build_version=true
        else
            echo -e "${YELLOW}  跳过: 没有构建版本信息${NC}"
            if [ "$REMOVE_CODESIGN" = false ]; then
                return 0
            fi
        fi
    fi
    
    # 显示当前版本信息（仅在有构建版本信息时）
    if [ "$has_build_version" = true ]; then
        echo -e "${BLUE}  当前版本信息:${NC}"
        vtool -show-build "$binary_path" 2>/dev/null | grep -E "(minos|sdk)" | sed 's/^/    /'
    fi
    
    # 检查代码签名状态
    local has_codesign=false
    if [ "$REMOVE_CODESIGN" = true ] || [ "$CODESIGN_ONLY" = true ]; then
        if codesign -dv "$binary_path" 2>/dev/null; then
            has_codesign=true
            echo -e "${BLUE}  当前签名状态: 已签名${NC}"
        else
            echo -e "${BLUE}  当前签名状态: 未签名${NC}"
        fi
    fi
    
    # 预览模式下不实际修改
    if [ "$DRY_RUN" = true ]; then
        if [ "$CODESIGN_ONLY" = true ]; then
            echo -e "${BLUE}  将要移除代码签名${NC}"
        else
            echo -e "${BLUE}  将要修改为:${NC}"
            echo -e "    minos ${MIN_VERSION}"
            echo -e "      sdk ${SDK_VERSION}"
            if [ "$REMOVE_CODESIGN" = true ]; then
                echo -e "${BLUE}  将要移除代码签名${NC}"
            fi
        fi
        echo -e "${YELLOW}  [预览模式] 跳过实际修改${NC}"
        echo ""
        return 0
    fi
    
    # 仅移除代码签名模式
    if [ "$CODESIGN_ONLY" = true ]; then
        if [ "$has_codesign" = true ]; then
            echo -e "${BLUE}  移除代码签名...${NC}"
            if codesign --remove-signature "$binary_path" 2>/dev/null; then
                echo -e "${GREEN}  ✓ 代码签名移除成功${NC}"
            else
                echo -e "${RED}  ✗ 代码签名移除失败${NC}"
                return 1
            fi
        else
            echo -e "${YELLOW}  跳过: 文件未签名${NC}"
        fi
        echo ""
        return 0
    fi
    
    # 如果没有构建版本信息但需要移除代码签名
    if [ "$has_build_version" = false ] && [ "$REMOVE_CODESIGN" = true ]; then
        if [ "$has_codesign" = true ]; then
            echo -e "${BLUE}  移除代码签名...${NC}"
            if codesign --remove-signature "$binary_path" 2>/dev/null; then
                echo -e "${GREEN}  ✓ 代码签名移除成功${NC}"
            else
                echo -e "${RED}  ✗ 代码签名移除失败${NC}"
                return 1
            fi
        fi
        echo ""
        return 0
    fi
    
    # 创建临时文件
    local temp_file="$TEMP_DIR/$(basename "$binary_path")_modified"
    
    # 修改构建版本信息
    if vtool -set-build-version "$PLATFORM" "$MIN_VERSION" "$SDK_VERSION" -tool "$TOOL_TYPE" "$TOOL_VERSION" -output "$temp_file" "$binary_path" 2>/dev/null; then
        # 替换原文件
        if cp "$temp_file" "$binary_path"; then
            echo -e "${GREEN}  ✓ 修改成功${NC}"
            
            # 验证修改结果
            echo -e "${BLUE}  新版本信息:${NC}"
            vtool -show-build "$binary_path" 2>/dev/null | grep -E "(minos|sdk)" | sed 's/^/    /'
            
            # 移除代码签名（如果启用）
            if [ "$REMOVE_CODESIGN" = true ]; then
                echo -e "${BLUE}  移除代码签名...${NC}"
                if codesign --remove-signature "$binary_path" 2>/dev/null; then
                    echo -e "${GREEN}  ✓ 代码签名移除成功${NC}"
                else
                    echo -e "${YELLOW}  ⚠ 代码签名移除失败（可能本来就没有签名）${NC}"
                fi
            fi
        else
            echo -e "${RED}  ✗ 替换文件失败${NC}"
            return 1
        fi
    else
        echo -e "${RED}  ✗ 修改失败${NC}"
        return 1
    fi
    
    echo ""
}

# 函数: 自动发现应用中的二进制文件
discover_binaries() {
    local app_path="$1"
    local binaries=()
    
    # 1. 主可执行文件
    local main_exec="$app_path/Contents/MacOS"
    if [ -d "$main_exec" ]; then
        while IFS= read -r -d '' file; do
            if [ -f "$file" ] && [ -x "$file" ]; then
                binaries+=("$file")
            fi
        done < <(find "$main_exec" -type f -perm +111 -print0)
    fi
    
    # 2. Frameworks 中的二进制文件
    local frameworks_dir="$app_path/Contents/Frameworks"
    if [ -d "$frameworks_dir" ]; then
        # Helper 应用
        while IFS= read -r -d '' helper_app; do
            local helper_exec="$helper_app/Contents/MacOS"
            if [ -d "$helper_exec" ]; then
                while IFS= read -r -d '' file; do
                    if [ -f "$file" ] && [ -x "$file" ]; then
                        binaries+=("$file")
                    fi
                done < <(find "$helper_exec" -type f -perm +111 -print0)
            fi
        done < <(find "$frameworks_dir" -name "*.app" -type d -print0)
        
        # Framework 二进制文件
        while IFS= read -r -d '' framework; do
            local framework_name=$(basename "$framework" .framework)
            local framework_binary="$framework/Versions/A/$framework_name"
            if [ -f "$framework_binary" ]; then
                binaries+=("$framework_binary")
            fi
            
            # Framework 中的其他可执行文件
            while IFS= read -r -d '' file; do
                if [ -f "$file" ] && [ -x "$file" ] && [[ "$file" != "$framework_binary" ]]; then
                    binaries+=("$file")
                fi
            done < <(find "$framework" -type f -perm +111 -print0)
        done < <(find "$frameworks_dir" -name "*.framework" -type d -print0)
    fi
    
    # 3. Resources 中的可执行文件
    local resources_dir="$app_path/Contents/Resources"
    if [ -d "$resources_dir" ]; then
        while IFS= read -r -d '' file; do
            if [ -f "$file" ] && [ -x "$file" ]; then
                binaries+=("$file")
            fi
        done < <(find "$resources_dir" -type f -perm +111 -print0)
    fi
    
    # 返回去重后的二进制文件列表
    printf '%s\n' "${binaries[@]}" | sort -u
}

# 查找所有需要处理的二进制文件
echo -e "${BLUE}自动发现二进制文件...${NC}"

# 使用自动发现功能
ALL_BINARIES=()
while IFS= read -r binary; do
    if [ -n "$binary" ]; then
        ALL_BINARIES+=("$binary")
    fi
done < <(discover_binaries "$APP_PATH")

echo -e "${BLUE}发现 ${#ALL_BINARIES[@]} 个二进制文件${NC}"

# 处理所有文件
SUCCESS_COUNT=0
FAIL_COUNT=0
SKIP_COUNT=0

echo -e "${BLUE}=== 开始处理 ===${NC}"

for binary in "${ALL_BINARIES[@]}"; do
    if [ -f "$binary" ]; then
        if process_binary "$binary"; then
            ((SUCCESS_COUNT++))
        else
            ((FAIL_COUNT++))
        fi
    else
        echo -e "${YELLOW}跳过: 文件不存在 - $(basename "$binary")${NC}"
        ((SKIP_COUNT++))
    fi
done

# 清理临时目录（仅在非预览模式下）
if [ "$DRY_RUN" = false ] && [ -n "$TEMP_DIR" ]; then
    echo -e "${BLUE}清理临时目录...${NC}"
    rm -rf "$TEMP_DIR"
fi

# 显示结果统计
echo -e "${BLUE}=== 处理完成 ===${NC}"
if [ "$DRY_RUN" = true ]; then
    echo -e "${YELLOW}预览模式: 发现 ${#ALL_BINARIES[@]} 个二进制文件${NC}"
    echo -e "${YELLOW}使用 '--dry-run' 参数移除预览模式以实际修改文件${NC}"
else
    echo -e "${GREEN}成功: $SUCCESS_COUNT 个文件${NC}"
    echo -e "${RED}失败: $FAIL_COUNT 个文件${NC}"
    echo -e "${YELLOW}跳过: $SKIP_COUNT 个文件${NC}"
fi

if [ "$DRY_RUN" = false ]; then
    if [ $FAIL_COUNT -eq 0 ]; then
        echo -e "${GREEN}✓ 所有文件处理完成！${NC}"
        exit 0
    else
        echo -e "${RED}✗ 部分文件处理失败${NC}"
        exit 1
    fi
fi
