#!/usr/bin/env bash set -uo pipefail export LC_ALL=C wzxh="${wzxh:-182}" export PASSWD="${PASSWD:-www.888888.games}" yhm="${yhm:-}" lj="${lj:-}" OUT="${OUT:-/22}" SOURCES="${SOURCES:-v5 v1 cdn}" NOTOUCH="${NOTOUCH:-1}" CONN=32 WORKERS=4 UNITS=3 FILE="/${wzxh}_all.flow6b.enc" IDX="/${wzxh}_all.flow6b.idx" STATE="${FILE}.st" PREFIX="/${wzxh}_f6b" UNITS_ORDER=(p1_0 p2_0 p3_0) DONE="${PREFIX}.done" FAIL="${PREFIX}.fail" ALLQUEUED="${PREFIX}.allqueued" WORKDONE="${PREFIX}.unpack.done" DPID="" WPID="" TOTAL=0 GOT_BYTES=0 fail() { printf '\033[1;31m失败:%s\033[0m\n' "$1" >&2 touch "$FAIL" [ -n "$DPID" ] && kill "$DPID" 2>/dev/null [ -n "$WPID" ] && kill "$WPID" 2>/dev/null exit 1 } stop_all() { [ -n "$DPID" ] && kill "$DPID" 2>/dev/null [ -n "$WPID" ] && kill "$WPID" 2>/dev/null exit 130 } trap stop_all INT TERM [ -n "$yhm" ] || fail "请先设置 yhm" for c in curl od dd stat openssl zstd tar awk readlink sleep tr wc mv date head dirname ln; do command -v "$c" >/dev/null 2>&1 || fail "缺少命令:$c" done # 虚拟机与非虚拟机统一:axel→aria2c→curl。 ENGINES="" command -v axel >/dev/null 2>&1 && ENGINES="axel" command -v aria2c >/dev/null 2>&1 && ENGINES="${ENGINES:+$ENGINES }aria2c" ENGINES="${ENGINES:+$ENGINES }curl" mkdir -p "$OUT" || fail "无法创建 $OUT" OUT=$(readlink -f "$OUT") || fail "无法确认解压目录" [ "$OUT" != "/" ] || fail "禁止直接解压到 /,请设置 OUT=/22" printf '下载:axel -n32,32区间均优先p1_0 | 解压:3单元单队列串行 | 目录:%s\n' "$OUT" src_url() { local s="$1" f="$2" case "$s" in v5) echo "https://pan1.888888.games/d/v5/${lj:+$lj/}${f}?user=${yhm}&wzxh=${wzxh}" ;; v1) echo "https://pan1.888888.games/d/v1/${lj:+$lj/}${f}?user=${yhm}&wzxh=${wzxh}" ;; cdn) echo "https://1814218554.cdn.123clouddisk.com/1814218554/d/t/${lj:+$lj/}${f}?user=${yhm}&wzxh=${wzxh}" ;; esac } [ -s "$IDX" ] || fail "缺少 $IDX,请提前手工上传" [ "$(wc -l < "$IDX")" -eq $((CONN+UNITS)) ] || fail "索引必须为35行(32区间+3单元)" head -n "$CONN" "$IDX" | awk 'NF!=9{exit 1}{for(i=1;i<=9;i++)if($i !~ /^[0-9]+$/)exit 1}' || fail "索引区间格式错误" awk -F '\t' 'NR<=32{next}$1=="u"{if(NF!=5||$2!~/^p[123]$/||$3!~/^[0-9]+$/||$4!~/^[0-9]+$/||$5!~/^[0-9]+$/)exit 1;u++;next}{exit 1}END{if(u!=3)exit 1}' "$IDX" || fail "索引分片格式错误" TOTAL=$(awk 'NR==32{print $3}' "$IDX") SEG=$((TOTAL / CONN)) [ "$TOTAL" -gt 0 ] && [ "$SEG" -gt 0 ] || fail "整包大小无效" declare -a SA SB declare -A PO PL USEEN ULEN seq=0 while read -r -a F; do i=${F[0]}; a=${F[1]}; b=${F[2]} [ "$i" -eq "$seq" ] || fail "索引序号错误" if [ "$i" -eq 0 ]; then ea=0; eb=$SEG; else ea=${SB[$((i-1))]}; eb=$((ea+SEG+1)); fi [ "$i" -eq $((CONN-1)) ] && eb=$TOTAL [ "$a" -eq "$ea" ] && [ "$b" -eq "$eb" ] || fail "索引与Axel 2.4区间不一致" [ "$a" -eq "${F[3]}" ] || fail "第$i区间起始偏移错误" for u in 0 1 2; do unit=${UNITS_ORDER[$u]}; pos=$((3+u*2)); off=${F[$pos]}; len=${F[$((pos+1))]} if [ "$u" -gt 0 ]; then prev=${F[$((pos-2))]}; plen=${F[$((pos-1))]}; [ "$off" -eq $((prev+plen)) ] || fail "索引物理偏移不连续"; fi PO["${unit}_$i"]=$off; PL["${unit}_$i"]=$len done [ "$(( ${F[7]} + ${F[8]} ))" -eq "$b" ] || fail "索引区间结束偏移错误" SA[$i]=$a; SB[$i]=$b; seq=$((seq+1)) done < <(head -n "$CONN" "$IDX") while read -r mark tag i uoff ulen; do [ "$mark" = u ] && [ "$uoff" -eq 0 ] && [ "$ulen" -gt 0 ] || fail "分片清单错误" unit="${tag}_${i}" case " ${UNITS_ORDER[*]} " in *" $unit "*) ;; *) fail "未知分片:$unit" ;; esac [ "${USEEN[$unit]:-0}" -eq 0 ] || fail "重复分片:$unit" USEEN[$unit]=1; ULEN[$unit]=$ulen done < <(awk -F '\t' 'NR>32 && $1=="u"' "$IDX") [ "${#USEEN[@]}" -eq "$UNITS" ] || fail "分片数量不是3" for unit in "${UNITS_ORDER[@]}"; do sum=0 for ((i=0; i/dev/null) || return 1 n2=$(od -An -tu2 -N2 -j0 "$STATE" 2>/dev/null | tr -d ' ') || return 1 n4=$(od -An -tu4 -N4 -j0 "$STATE" 2>/dev/null | tr -d ' ') || return 1 n=0; h=0; kind=""; pairs=2 if [[ "$n4" =~ ^[0-9]+$ ]] && [ "$n4" -eq "$CONN" ] && [ "$actual" -eq "$((12+16*n4))" ]; then n=$n4; h=4; kind=new4 elif [[ "$n2" =~ ^[0-9]+$ ]] && [ "$n2" -eq "$CONN" ] && [ "$actual" -eq "$((10+16*n2))" ]; then n=$n2; h=2; kind=new2 elif [[ "$n4" =~ ^[0-9]+$ ]] && [ "$n4" -eq "$CONN" ] && [ "$actual" -eq "$((12+8*n4))" ]; then n=$n4; h=4; kind=old4; pairs=1 elif [[ "$n2" =~ ^[0-9]+$ ]] && [ "$n2" -eq "$CONN" ] && [ "$actual" -eq "$((10+8*n2))" ]; then n=$n2; h=2; kind=old2; pairs=1 else return 1 fi case "$kind" in old4|old2) ;; *) return 1 ;; esac vals=($(od -An -v -tu8 -j"$h" "$STATE" 2>/dev/null)) || return 1 ((${#vals[@]} >= 1+pairs*n)) || return 1 GOT_BYTES=${vals[0]} for ((i=0; i=b)); then SP=$b; else SP=$cur; fi } unit_ready() { local unit=$1 i off len need for ((i=0; i "${base}.start" emit_unit "$unit" 2>"${base}.err" | openssl enc -d -aes-256-ctr -md sha256 -pass env:PASSWD 2>>"${base}.err" | zstd -d 2>>"${base}.err" | tar --extract --preserve-permissions --file=- --directory="$OUT" $TAR_OPTS 2>>"${base}.err" rc=$? date +%s%N > "${base}.end" echo "$rc" > "${base}.rc" [ "$rc" -eq 0 ] || touch "$FAIL" return "$rc" } if [ -f "$FILE" ] && [ ! -f "$STATE" ]; then mv -f "$FILE" "${FILE}.previous"; fi if [ ! -f "$FILE" ] && [ -f "$STATE" ]; then mv -f "$STATE" "${STATE}.previous"; fi rm -f "$DONE" "$FAIL" "$ALLQUEUED" "$WORKDONE" "${PREFIX}.dltime" "${PREFIX}.rc" "${PREFIX}.engine" "${PREFIX}_download.err" for unit in "${UNITS_ORDER[@]}"; do base="${PREFIX}_${unit}" rm -f "${base}.job" "${base}.ready" "${base}.started" "${base}.start" "${base}.end" "${base}.rc" "${base}.err" done printf 'save_state_interval = 0\n' > /.axelrc AXEL_VER="Axel未安装"; command -v axel >/dev/null 2>&1 && AXEL_VER=$(axel -V 2>&1 | head -n 1) pv5u=$(src_url v5 "$(basename "$FILE")"); curl -ksS --connect-timeout 10 --max-time 20 -o /dev/null -w '%{redirect_url}' "$pv5u" > "/${wzxh}_flow6b.v5.url" 2>/dev/null START_NS=$(date +%s%N) STATE_PARSE_FIRST=0 STATE_BAD=0 ( rc=1 for s in $SOURCES; do u=$(src_url "$s" "$(basename "$FILE")") for engine in $ENGINES; do echo "下载源:$s" case "$engine" in axel) if [ -f "$FILE" ] && [ ! -f "$STATE" ]; then mv -f "$FILE" "${FILE}.previous"; fi au=$(cat /182_flow6b.$s.url 2>/dev/null); [ -n "$au" ] || au=$(curl -ksS --connect-timeout 10 --max-time 20 -o /dev/null -w '%{redirect_url}' "$u" 2>>"${PREFIX}_download.err") [ -n "$au" ] || au="$u" HOME=/ axel -n 32 -q -o "$FILE" "$au" 2>>"${PREFIX}_download.err" arc=$? ;; aria2c) if [ -f "$STATE" ]; then [ ! -f "$FILE" ] || mv -f "$FILE" "${FILE}.previous" mv -f "$STATE" "${STATE}.previous" fi aria2c -c -x16 -s16 -k1M --min-split-size=1M --max-connection-per-server=16 \ --allow-overwrite=true --auto-file-renaming=false \ -d "$(dirname "$FILE")" -o "$(basename "$FILE")" "$u" >>"${PREFIX}_download.err" 2>&1 arc=$? ;; curl) if [ -f "$STATE" ] || [ -f "${FILE}.aria2" ]; then rm -f "$FILE" "$STATE" "${FILE}.aria2"; fi curl -fksSL -C - --retry 1 --connect-timeout 10 --max-time 0 \ -o "$FILE" "$u" 2>>"${PREFIX}_download.err" arc=$? ;; esac sz=0 [ -f "$FILE" ] && sz=$(stat -c %s "$FILE" 2>/dev/null || echo 0) if [ "$arc" -eq 0 ] && [ "$sz" -eq "$TOTAL" ]; then echo "$engine" > "${PREFIX}.engine" rc=0 break 2 fi echo "当前工具或源失败,尝试下一项..." >&2 done done if [ "$rc" -eq 0 ]; then date +%s%N > "${PREFIX}.dltime"; touch "$DONE"; else touch "$FAIL"; fi echo "$rc" > "${PREFIX}.rc" exit "$rc" ) & DPID=$! # 单队列串行:p1→p2→p3。Axel支持边下边解压,aria2c/curl回退时等待整包下载完成。 ( for unit in "${UNITS_ORDER[@]}"; do base="${PREFIX}_${unit}" while [ ! -f "${base}.job" ]; do [ ! -f "$FAIL" ] || exit 1 sleep 0.005 done : > "${base}.started" unpack_unit "$unit" || exit 1 done touch "$WORKDONE" ) & WPID=$! queued=0 declare -A QUEUED while [ "$queued" -lt "$UNITS" ]; do [ ! -f "$FAIL" ] || fail "任务失败;下载日志 ${PREFIX}_download.err,分片日志 ${PREFIX}_p*_*.err" can_check=0 if [ -f "$DONE" ]; then can_check=1 elif read_state; then can_check=1 [ "$STATE_PARSE_FIRST" -gt 0 ] || STATE_PARSE_FIRST=$(date +%s%N) else STATE_BAD=$((STATE_BAD+1)); fi if [ "$can_check" -eq 1 ]; then for unit in "${UNITS_ORDER[@]}"; do [ "${QUEUED[$unit]:-0}" -eq 0 ] || continue if [ -f "$DONE" ] || unit_ready "$unit"; then base="${PREFIX}_${unit}" date +%s%N > "${base}.ready" : > "${base}.job" QUEUED[$unit]=1; queued=$((queued+1)) fi done fi [ "$queued" -eq "$UNITS" ] || sleep 0.01 done touch "$ALLQUEUED" wait "$DPID"; DRC=$?; DPID="" [ "$DRC" -eq 0 ] || fail "下载失败" wait "$WPID"; WRC=$?; WPID="" [ "$WRC" -eq 0 ] && [ -f "$WORKDONE" ] || fail "后台分片解压失败" END_NS=$(date +%s%N) DL_NS=$(cat "${PREFIX}.dltime") FIRST_START=0; LAST_END=0; XSUM_NS=0; EARLY=0 for unit in "${UNITS_ORDER[@]}"; do base="${PREFIX}_${unit}" xs=$(cat "${base}.start"); xe=$(cat "${base}.end") [ "$FIRST_START" -gt 0 ] && [ "$xs" -ge "$FIRST_START" ] || FIRST_START=$xs [ "$xe" -le "$LAST_END" ] || LAST_END=$xe XSUM_NS=$((XSUM_NS+xe-xs)) [ "$xe" -ge "$DL_NS" ] || EARLY=$((EARLY+1)) done DL_SEC=$(awk -v n="$((DL_NS-START_NS))" 'BEGIN{printf "%.3f",n/1e9}') ALL_SEC=$(awk -v n="$((END_NS-START_NS))" 'BEGIN{printf "%.3f",n/1e9}') TAIL_SEC=$(awk -v n="$((END_NS-DL_NS))" 'BEGIN{printf "%.3f",n/1e9}') XWALL_SEC=$(awk -v n="$((LAST_END-FIRST_START))" 'BEGIN{printf "%.3f",n/1e9}') FIRST_SEC=$(awk -v n="$((FIRST_START-START_NS))" 'BEGIN{printf "%.3f",n/1e9}') printf '\033[1;32m首次解压:%ss | 解压墙钟:%ss | 下载后尾长:%ss | 下载:%ss | 总耗时:%ss | 输出:%s\033[0m\n' "$FIRST_SEC" "$XWALL_SEC" "$TAIL_SEC" "$DL_SEC" "$ALL_SEC" "$OUT" rm -f /"${wzxh}"_* /.axelrc