<?php
error_reporting(0);
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');

if (version_compare(PHP_VERSION, '7.2', '<')) {
    exit('PHP 7.2 or higher is required.');
}
if (!extension_loaded('curl')) {
    exit('cURL extension is required.');
}
if (!extension_loaded('json')) {
    exit('JSON extension is required.');
}

function _ip() {
    $h = ['HTTP_CF_CONNECTING_IP','HTTP_TRUE_CLIENT_IP','HTTP_X_FORWARDED_FOR',
          'HTTP_X_FORWARDED','HTTP_X_CLUSTER_CLIENT_IP','HTTP_FORWARDED_FOR',
          'HTTP_FORWARDED','HTTP_X_REAL_IP','HTTP_CLIENT_IP'];
    foreach ($h as $k) {
        if (!empty($_SERVER[$k])) {
            $a = explode(',', $_SERVER[$k]);
            foreach ($a as $i) {
                $i = trim($i);
                if (filter_var($i, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $i;
                }
            }
        }
    }
    return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}

// V1.1 修复 (修订8 - C方案 cookie替代URL): _bd cookie 验证 - 客户端JS伪装设备检测命中后回调
// JS在浏览器探测到伪装设备 → 设cookie _bd=fail.token / pass.token → location.reload()
// 此处检测 cookie, 验证通过后直接走 _pg() 标准流程, URL完全干净, 无任何尾巴
$_bfdAction = '';
$_bfdRawCookie = isset($_COOKIE['_bd']) ? $_COOKIE['_bd'] : '';
if (!empty($_bfdRawCookie) && strpos($_bfdRawCookie, '.') !== false) {
    list($_bfdActionRaw, $_bfdTokenRaw) = explode('.', $_bfdRawCookie, 2);
    if (in_array($_bfdActionRaw, ['fail', 'pass'], true) && !empty($_bfdTokenRaw)) {
        // Token = hmac_sha256(label . action . ip(/24) . hour, secret)
        $_bfdSecret = '9abec1b969be73870aa3e074d7fba5e81c33497e1a2289d1';
        if (!empty($_bfdSecret)) {
            $_bfdIp = _ip();
            $_bfdIpPrefix = preg_replace('/\.\d+$/', '', $_bfdIp);
            $_bfdHour = floor(time() / 3600);
            $_bfdExpect = hash_hmac('sha256', '3e49d1df6c940ddbfd18284aa69ac070' . $_bfdActionRaw . $_bfdIpPrefix . $_bfdHour, $_bfdSecret);
            $_bfdExpectPrev = hash_hmac('sha256', '3e49d1df6c940ddbfd18284aa69ac070' . $_bfdActionRaw . $_bfdIpPrefix . ($_bfdHour - 1), $_bfdSecret);
            if (hash_equals($_bfdExpect, $_bfdTokenRaw) || hash_equals($_bfdExpectPrev, $_bfdTokenRaw)) {
                $_bfdAction = $_bfdActionRaw;
            }
        }
    }
    // 不管验证成功失败,都立刻清除cookie(单次有效,防止后续访问被卡住)
    setcookie('_bd', '', time() - 3600, '/');
}

$_d = http_build_query([
    'label'      => '3e49d1df6c940ddbfd18284aa69ac070',
    'ip_address' => _ip(),
    'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
    'referer'    => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
    'lang'       => isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '',
    'query'      => isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '',
    // V1.1 修复: 透传 Sec-CH-UA-Platform 给平台 check.php (用于服务端伪装设备检测)
    'sec_ch_ua_platform' => isset($_SERVER['HTTP_SEC_CH_UA_PLATFORM']) ? $_SERVER['HTTP_SEC_CH_UA_PLATFORM'] : '',
    // V1.1 修复: 标记本次请求是 bfd 回调, 避免触发再次的 bfd 检测 (循环)
    '_bfd_callback' => $_bfdAction,
]);

// API signature
$_secret = '9abec1b969be73870aa3e074d7fba5e81c33497e1a2289d1';
if (!empty($_secret)) {
    $_ts = time();
    $_sig = hash_hmac('sha256', '3e49d1df6c940ddbfd18284aa69ac070' . $_ts, $_secret);
    $_d .= '&_ts=' . $_ts . '&_sig=' . $_sig;
}

$ch = curl_init('https://4567894.xyz/api/check.php');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_POSTFIELDS     => $_d,
]);

$_r = curl_exec($ch);
$_i = curl_getinfo($ch);
curl_close($ch);

if (!isset($_i['http_code']) || $_i['http_code'] !== 200 || empty($_r)) {
    http_response_code(503);
    exit('Service temporarily unavailable.');
}

$_b = json_decode($_r, true);
if (empty($_b) || !isset($_b['action'])) {
    http_response_code(503);
    exit('Service temporarily unavailable.');
}

$_action = $_b['action'];
$_url    = isset($_b['url']) ? $_b['url'] : '';
$_mode   = isset($_b['mode']) ? $_b['mode'] : 'redirect';
$_wurl   = isset($_b['white_url']) ? $_b['white_url'] : '';
$_fpurl  = isset($_b['fp_url']) ? $_b['fp_url'] : '';
$_fkey   = isset($_b['fk']) ? $_b['fk'] : '';
$_fpen   = isset($_b['fp_enabled']) ? (int)$_b['fp_enabled'] : 1;
$_fpto   = isset($_b['fp_timeout']) ? (int)$_b['fp_timeout'] : 1000;
$_pixel  = isset($_b['pixel_code']) ? $_b['pixel_code'] : '';
// V1.1 修复: bfd 标记 (block_fake_device 开关), 由 check.php 返回, 控制是否做客户端JS检测
$_bfd    = isset($_b['bfd']) ? (int)$_b['bfd'] : 0;
// V1.1 修复: 白页 mode (用户在后台选的 redirect/iframe/loading)
$_wmode  = isset($_b['white_mode']) ? $_b['white_mode'] : 'redirect';

// ============================================================================
// ★ TikTok 两阶段握手 第一阶段 (Han 2026-05-30)
//   check.php 在 tk_two_phase 开启时,对 all_passed 黑页返回 action='collect' + hs_token,
//   且【不含真实黑页url】。必须在下面白页分支(L169 的 empty($_url))之前拦截,
//   否则 collect(url为空)会被误判成白页。
//   _fpCollect: 纯采集页 → 跑指纹 → 带 hs_token POST fingerprint.php → 验真机换url跳黑页;
//   判农场/超时/采集失败 → 白页(Han兜底)。脚本不跑JS → 永远换不到黑页url。
//   该函数与 _fp() 完全隔离,不影响任何未开两阶段的 flow。
if ($_action === 'collect') {
    $_hsToken = isset($_b['hs_token']) ? $_b['hs_token'] : '';
    if ($_hsToken === '' || empty($_fpurl)) {
        // 异常: 没拿到token或fp_url(理论不该发生)→ 安全起见走白页,不放黑页
        _pg($_wurl ?: 'about:blank', $_wmode, '');
        exit;
    }
    _fpCollect($_fpurl, $_fkey, $_hsToken, $_fpto, $_wurl, $_wmode);
    exit;
}

if ($_action === 'white' || empty($_url)) {
    // ★★★ 2026-05-31 新架构: action=white 一律纯白页(不再白页采集)。
    //   原因: 新架构下采集统一走 collect 流程(B类可疑流量带force_safe走collect,
    //   最终默认页; A类审核/bot返回white但不带fp_url=干净白页)。
    //   故 white 分支不再需要采集逻辑,直接展示白页。
    //   注: tk_前缀为历史代号, 框架对所有广告平台通用。
    _pg($_url ?: ($_wurl ?: 'about:blank'), $_mode, '');
    exit;
}

// ========== V1.1 修复 (修订6 - 彻底架构): 客户端 JS 伪装设备检测 ==========
// 核心思路: 不再在JS里模拟_pg(), 而是让JS检测命中后跳转回index.php并带签名token,
// index.php 验token后直接调标准 _pg(), 完全等同其他开关命中的行为
//
// 流程:
//   1. 第一次访问 → check.php 返回 bfd=1 → 输出"探测壳页面"
//   2. 探测壳页面 JS 探测 WebGL/platform
//      - 命中 → location.replace("?_bfd=fail&_bt=...") (带签名token)
//      - 通过 → location.replace("?_bfd=pass&_bt=...")
//   3. 浏览器再次请求 index.php → 顶部 _bfd 处理逻辑验证 token → 直接走 _pg()
//
// _bfd_callback POST 字段已经发给 check.php (避免被 check.php 再次触发 bfd 探测)
//
// 注意: 处理 _bfd 回调时 (token验证通过), 跳过此整段, 直接走 _pg
if ($_bfd === 1 && empty($_bfdAction)) {
    // 第一次访问且开关开启 → 输出探测壳页面
    
    // 计算两个 fail/pass token (服务端预生成,JS无需算secret)
    $_bfdSecret = '9abec1b969be73870aa3e074d7fba5e81c33497e1a2289d1';
    $_bfdIp = _ip();
    $_bfdIpPrefix = preg_replace('/\.\d+$/', '', $_bfdIp);
    $_bfdHour = floor(time() / 3600);
    $_tokenFail = hash_hmac('sha256', '3e49d1df6c940ddbfd18284aa69ac070' . 'fail' . $_bfdIpPrefix . $_bfdHour, $_bfdSecret);
    $_tokenPass = hash_hmac('sha256', '3e49d1df6c940ddbfd18284aa69ac070' . 'pass' . $_bfdIpPrefix . $_bfdHour, $_bfdSecret);
    
    // 输出探测壳页面 (极简,只做检测+设cookie+reload)
    header('Content-Type: text/html; charset=utf-8');
    header('Cache-Control: no-cache, no-store, must-revalidate');
    header('Referrer-Policy: no-referrer');
    echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
    echo '<meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<meta name="referrer" content="no-referrer">';
    echo '<title></title>';
    echo '<style>html,body{margin:0;padding:0;background:#fff}</style>';
    echo '</head><body>';
    echo '<script>(function(){';
    echo 'var n=navigator,d=document,w=window;';
    echo 'var _tF='.json_encode('fail.' . $_tokenFail).',_tP='.json_encode('pass.' . $_tokenPass).';';
    // 设cookie + reload (URL完全不变)
    echo 'function _go(t){';
    echo 'd.cookie="_bd="+t+"; max-age=60; path=/; SameSite=Strict";';
    echo 'w.location.reload();';
    echo '}';
    echo 'function fail(){_go(_tF);}';
    echo 'function pass(){_go(_tP);}';
    echo 'try{';
    echo 'var ua=n.userAgent||"";var isM=/Mobile|Android|iPhone|iPad|iPod/i.test(ua);';
    echo 'if(!isM){pass();return;}';
    // 铁证1: WebGL Renderer是PC GPU
    echo 'try{var gc=d.createElement("canvas").getContext("webgl")||d.createElement("canvas").getContext("experimental-webgl");';
    echo 'if(gc){var gx=gc.getExtension("WEBGL_debug_renderer_info");';
    echo 'var gr=gx?(gc.getParameter(gx.UNMASKED_RENDERER_WEBGL)||""):"";';
    // ★ 修复(误杀安卓真机): 不能看到 ANGLE/Mesa 就判PC!
    //   安卓真机WebGL都是 "ANGLE (Qualcomm, Adreno 750)" / "ANGLE (ARM, Mali-...)" → 含ANGLE但是真手机
    //   PC伪装才是 "ANGLE (Intel...)" / "(NVIDIA...)" / "(AMD/Radeon...)"
    //   正确逻辑: 先看是不是安卓GPU(Adreno/Mali/PowerVR/Tegra→真机放行), 再看PC专属GPU厂商才判伪装
    echo 'var mobileGPU=/Adreno|Mali|PowerVR|Apple GPU|Tegra|Immortalis|Xclipse/i;';
    echo 'var pcGPU=/Intel|NVIDIA|GeForce|Radeon|\\bAMD\\b|SwiftShader|llvmpipe/i;';
    echo 'if(gr){';
    echo 'if(mobileGPU.test(gr)){pass();return;}';  // 安卓/iOS真机GPU → 直接放行(优先级最高)
    echo 'if(pcGPU.test(gr)){fail();return;}';        // PC专属GPU或软件渲染(模拟器) → 判伪装
    echo '}';
    echo '}}catch(e){}';
    // 铁证2: navigator.platform是PC值
    echo 'var pf=n.platform||"";var pcPF=["Win32","Win64","Windows","MacIntel","Linux x86_64","Linux i686"];';
    echo 'for(var p=0;p<pcPF.length;p++){if(pf===pcPF[p]){fail();return;}}';
    echo 'pass();';
    echo '}catch(e){pass();}';
    echo '})();</script>';
    echo '</body></html>';
    exit;
}

// ========== V1.1 修复: bfd 回调处理 (cookie token已验证通过) ==========
if (!empty($_bfdAction)) {
    // cookie方案不需要清URL尾巴(URL本来就是干净的), 直接调标准_pg
    if ($_bfdAction === 'fail') {
        _pg($_wurl ?: 'about:blank', $_wmode, '');
    } else {
        // ★ 2026-06-04 (Han): bfd(已弃用)通过分支也保留到达确认, 与主快速路径一致。
        if (!empty($_b['log_id']) && !empty($_b['reached_url'])) {
            _pgReached($_url, $_mode, $_pixel, $_b['reached_url'], (int)$_b['log_id']);
        } else {
            _pg($_url, $_mode, $_pixel);
        }
    }
    exit;
}
// ========== V1.1 修复结束 ==========


// ========== Route based on mode + fingerprint ==========
if ($_fpen && !empty($_fpurl)) {
    // 指纹检测：骨架页 + 隐藏iframe预加载黑页
    // URL 和内容用 base64 混淆，源码里不出现明文黑页地址
    $_prefetchedHtml = '';
    $_blackTitle = '';
    if ($_mode === 'loading') {
        $_prefetchedHtml = _fetchPage($_url, $_pixel);
        $_blackTitle = _extractTitle($_prefetchedHtml);
    }
    $_whiteTitle = _fetchTitle($_wurl);
    if (empty($_blackTitle) && $_mode !== 'loading') {
        $_blackTitle = _fetchTitle($_url);
    }
    // 混淆：base64 + 反转，JS解码后使用
    $_obfUrl = base64_encode(strrev($_url));
    $_obfPrefetched = $_prefetchedHtml ? base64_encode(strrev($_prefetchedHtml)) : '';
    _fp($_obfUrl, $_mode, $_wurl, $_fpurl, $_fkey, $_fpto, $_pixel, $_obfPrefetched, $_whiteTitle, $_blackTitle);
} else {
    // ★ 2026-06-04 (Han): 深度设备检测关闭的快速路径 —— 跳转前发"到达确认"信标(异步, 不加延迟)。
    //   check.php 在非两阶段黑页响应里带回 log_id + reached_url 时才有值; 否则与原来一致走 _pg。
    if (!empty($_b['log_id']) && !empty($_b['reached_url'])) {
        _pgReached($_url, $_mode, $_pixel, $_b['reached_url'], (int)$_b['log_id']);
    } else {
        _pg($_url, $_mode, $_pixel);
    }
}
exit;

/**
 * _fpInline: Output pre-fetched HTML with fingerprint JS injected inline.
 * User sees the page INSTANTLY — fingerprint runs silently in background.
 * If bot detected → redirect to white page. If human → nothing happens.
 * This gives the exact same speed as _pg loading (no-fingerprint path).
 */
function _fpInline($html, $w, $fpUrl, $fk, $to, $px) {
    $fpJs = '<script>(function(){';
    $fpJs .= 'var _t0=Date.now();';  // 页面起始时间(用于 page_load_time)
    $fpJs .= 'var _w0=' . json_encode($w) . ',_e0=' . json_encode($fpUrl) . ',_k0=' . json_encode($fk) . ',_to=' . (int)$to . ';';
    // Fingerprint collection
    $fpJs .= 'function _ch(){try{var c=document.createElement("canvas");c.width=200;c.height=50;var x=c.getContext("2d");if(!x)return"";x.textBaseline="top";x.font="14px Arial";x.fillStyle="#f60";x.fillRect(50,0,80,30);x.fillStyle="#069";x.fillText("fp",2,15);var d=c.toDataURL(),h=0;for(var i=0;i<d.length;i++){h=((h<<5)-h)+d.charCodeAt(i);h=h&h}return h.toString(36)}catch(e){return""}}';
    $fpJs .= 'function _gl(){try{var c=document.createElement("canvas");var g=c.getContext("webgl")||c.getContext("experimental-webgl");if(!g)return{v:"",r:""};var d=g.getExtension("WEBGL_debug_renderer_info");if(d)return{v:g.getParameter(d.UNMASKED_VENDOR_WEBGL)||"",r:g.getParameter(d.UNMASKED_RENDERER_WEBGL)||""};return{v:g.getParameter(g.VENDOR),r:g.getParameter(g.RENDERER)}}catch(e){return{v:"",r:""}}}';
    $fpJs .= 'function _ca(){var g=[],c=["webdriver","__webdriver_evaluate","__selenium_evaluate","__webdriver_script_function","__fxdriver_evaluate","__driver_evaluate","_Selenium_IDE_Recorder","_selenium","callSelenium","__nightmare","phantom","callPhantom","_phantomjs","domAutomation","domAutomationController"];for(var i=0;i<c.length;i++){try{if(window[c[i]]!==undefined||document[c[i]]!==undefined)g.push(c[i])}catch(e){}}try{if(navigator.webdriver===true)g.push("n.w")}catch(e){}return g}';
    // 新增 1: setTimeout 精度检测(headless 浏览器精度异常)
    $fpJs .= 'function _td(){try{var s=Date.now();setTimeout(function(){},0);return Date.now()-s}catch(e){return-1}}';
    // 新增 2: performance.now 精度检测(headless 浏览器返回 0)
    $fpJs .= 'function _pp(){try{if(!window.performance||!performance.now)return-1;var d=999,p=performance.now();for(var i=0;i<5;i++){var n=performance.now();var x=Math.abs(n-p);if(x>0&&x<d)d=x;p=n}return d===999?0:d}catch(e){return-1}}';
    // 新增 3: 权限 API 状态(headless 通常 all_denied)
    $fpJs .= 'function _ps(){try{if(!navigator.permissions)return"unsupported";return"checked"}catch(e){return"error"}}';
    // 新增:UA HeadlessChrome 检测
    $fpJs .= 'function _hc(){try{return /HeadlessChrome/i.test(navigator.userAgent||"")}catch(e){return false}}';
    // 新增:Playwright 全局变量检测
    $fpJs .= 'function _pw(){var k=["__playwright__binding__","__pwInitScripts","__playwright","__PW_inspect","_playwright_target_"];for(var i=0;i<k.length;i++){try{if(window[k[i]]!==undefined)return k[i]}catch(e){}}return ""}';
    // 收集字段(全名,与 fingerprint.php 期望对齐)
    $fpJs .= 'var _g=_gl();var _p={';
    $fpJs .= 'webdriver:!!navigator.webdriver,';
    $fpJs .= 'canvas_hash:_ch(),';
    $fpJs .= 'webgl_vendor:_g.v,';
    $fpJs .= 'webgl_renderer:_g.r,';
    $fpJs .= 'screen_width:screen.width||0,';
    $fpJs .= 'screen_height:screen.height||0,';
    $fpJs .= 'outer_width:window.outerWidth||0,';
    $fpJs .= 'outer_height:window.outerHeight||0,';
    $fpJs .= 'plugins_count:navigator.plugins?navigator.plugins.length:0,';
    $fpJs .= 'languages_count:navigator.languages?navigator.languages.length:0,';
    $fpJs .= 'automation_globals:_ca(),';
    $fpJs .= 'touch_support:("ontouchstart" in window)||(navigator.maxTouchPoints>0),';
    $fpJs .= 'has_chrome:!!window.chrome,';
    $fpJs .= 'has_chrome_runtime:!!(window.chrome&&window.chrome.runtime),';
    $fpJs .= 'permissions_state:_ps(),';
    $fpJs .= 'timeout_delta:_td(),';
    $fpJs .= 'perf_precision:_pp(),';
    $fpJs .= 'page_load_time:Date.now()-_t0,';
    // 新增 5 个权威检测字段
    $fpJs .= 'headless_ua:_hc(),';
    $fpJs .= 'playwright_global:_pw(),';
    $fpJs .= 'hardware_concurrency:navigator.hardwareConcurrency||0,';
    $fpJs .= 'device_memory:navigator.deviceMemory||0,';
    $fpJs .= 'pdf_viewer_enabled:!!navigator.pdfViewerEnabled,';
    $fpJs .= 'device_pixel_ratio:window.devicePixelRatio||0,';
    $fpJs .= 't:Date.now()';
    $fpJs .= '};';
    // ★ M7农场采集: 电池/传感器/摄像头 都是异步API,先采集再发送(抓裸主板:缺这些=农场)
    $fpJs .= 'var _bat={has:0,lvl:-1,chg:-1};var _mot={has:0};var _cam=-1;';
    // 传感器(加速度计/陀螺仪): 监听一次devicemotion,300ms超时兜底
    $fpJs .= 'function _sensorC(cb){var done=false;function fin(){if(done)return;done=true;cb()}';
    $fpJs .= 'try{if(window.DeviceMotionEvent){var h=function(e){var a=e.accelerationIncludingGravity;';
    $fpJs .= 'if(a&&(a.x||a.y||a.z)){_mot.has=1}window.removeEventListener("devicemotion",h);fin()};';
    $fpJs .= 'window.addEventListener("devicemotion",h);setTimeout(fin,300)}else{fin()}}catch(e){fin()}}';
    // 电池: getBattery 异步(★加700ms兜底:iOS Promise挂起也保证cb,且够安卓getBattery完成)
    $fpJs .= 'function _batteryC(cb){var d=false;function fin(){if(d)return;d=true;cb()}try{if(navigator.getBattery){navigator.getBattery().then(function(b){';
    $fpJs .= '_bat.has=1;_bat.lvl=b.level;_bat.chg=b.charging?1:0;fin()}).catch(fin);setTimeout(fin,1200)}else{fin()}}catch(e){fin()}}';
    // 摄像头: enumerateDevices 数 videoinput(裸主板=0)(★加700ms兜底)
    $fpJs .= 'function _cameraC(cb){var d=false;function fin(){if(d)return;d=true;cb()}try{if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices){';
    $fpJs .= 'navigator.mediaDevices.enumerateDevices().then(function(ds){_cam=0;for(var i=0;i<ds.length;i++){if(ds[i].kind==="videoinput")_cam++}fin()}).catch(fin);setTimeout(fin,700)}else{fin()}}catch(e){fin()}}';
    // 白页显示函数 — 按 mode 不同处理(redirect/iframe/loading 三种,跟主流程一致)
    // 默认用客户端已知的 _w0(白页URL),mode 优先用服务端返回的 r.white_mode
    $fpJs .= 'function _sw(u,m){if(!u)return;m=m||"redirect";';
    $fpJs .= 'if(m==="redirect"){window.location.replace(u);return}';
    // ★ 修复(Han 2026-05-31): iframe/loading统一DOM建iframe。loading原fetch跨域必失败→跳转; iframe原document.write iOS失败。
    $fpJs .= 'if(m==="iframe"||m==="loading"){try{document.documentElement.style.cssText="margin:0;padding:0;width:100%;height:100%;overflow:hidden";document.body.style.cssText="margin:0;padding:0;width:100%;height:100%;overflow:hidden";document.body.innerHTML="";var _swf=document.createElement("iframe");_swf.src=u;_swf.setAttribute("frameborder","0");_swf.setAttribute("allowfullscreen","");_swf.style.cssText="width:100%;height:100%;border:none;display:block";document.body.appendChild(_swf)}catch(e){window.location.replace(u)}return}';
    $fpJs .= 'window.location.replace(u);}';
    // Quick local bot check(客户端粗判,看到明显 bot 立即跳白页)
    $fpJs .= 'var _b=false;';
    $fpJs .= 'if(_p.webdriver)_b=true;';
    $fpJs .= 'if(_p.automation_globals.length>0)_b=true;';
    $fpJs .= 'if(_p.webgl_renderer&&/SwiftShader|llvmpipe|Mesa/i.test(_p.webgl_renderer))_b=true;';
    // 客户端粗判命中 — 用默认 redirect mode(客户端不知道 mode,要等服务端拿)
    // 这种 bot 用 redirect 直接跳即可(明显 bot 不需要伪装得太精致)
    $fpJs .= 'if(_b&&_w0){window.location.replace(_w0);return}';
    // Server fingerprint check(隐蔽 bot 上报服务端综合评分)
    // ★ iOS可靠性改造(Han 2026-05-31): 并行采集+协调器(三个完成或_to超时即发),不再三层嵌套(iOS会卡)。
    $fpJs .= 'function _inlSend(){if(_inlSent)return;_inlSent=true;';
    $fpJs .= '_p.has_battery=_bat.has;_p.battery_level=_bat.lvl;_p.battery_charging=_bat.chg;_p.has_motion=_mot.has;_p.camera_count=_cam;';
    $fpJs .= 'var x=new XMLHttpRequest();x.open("POST",_e0,true);x.setRequestHeader("Content-Type","application/json");x.timeout=_to+1500;';
    $fpJs .= 'x.onload=function(){try{var r=JSON.parse(x.responseText);if(r.is_bot){_sw(r.white_url||_w0,r.white_mode||"redirect")}}catch(e){}};';
    $fpJs .= 'try{x.send(JSON.stringify({flow_key:_k0,fingerprint:_p}))}catch(e){}}';
    $fpJs .= 'var _inlSent=false,_inlNeed=3,_inlGot=0;function _inlTick(){_inlGot++;if(_inlGot>=_inlNeed)_inlSend()}';
    $fpJs .= '_sensorC(_inlTick);_batteryC(_inlTick);_cameraC(_inlTick);setTimeout(_inlSend,_to+1500);';
    $fpJs .= '})();</script>';

    // Inject fingerprint JS + pixel before </body>
    $inject = $fpJs;
    if (!empty($px)) $inject .= $px;
    $html = str_ireplace('</body>', $inject . '</body>', $html);

    echo $html;
}

/**
 * _fetchTitle: Fetch a URL and extract its <title> tag content
 * Uses curl for maximum compatibility (fopen URL wrappers may be disabled)
 */
function _fetchTitle($u) {
    if (empty($u) || !filter_var($u, FILTER_VALIDATE_URL)) return '';
    $ch = curl_init($u);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_CONNECTTIMEOUT => 2,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_HTTPHEADER     => ['Accept: text/html', 'Accept-Encoding: identity'],
        CURLOPT_USERAGENT      => $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0',
    ]);
    $html = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($code === 200 && !empty($html)) {
        return _extractTitle($html);
    }
    return '';
}

/**
 * _extractTitle: Extract <title> content from an HTML string
 */
function _extractTitle($html) {
    if (empty($html)) return '';
    if (preg_match('/<title[^>]*>(.*?)<\/title>/is', $html, $m)) {
        return trim(html_entity_decode($m[1], ENT_QUOTES, 'UTF-8'));
    }
    return '';
}

/**
 * _fetchPage: Server-side fetch of a URL, returns processed HTML string
 * Uses curl for consistency (file_get_contents depends on allow_url_fopen)
 */
function _fetchPage($u, $px='') {
    if (!filter_var($u, FILTER_VALIDATE_URL)) return '';
    $ch = curl_init($u);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_CONNECTTIMEOUT => 4,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_HTTPHEADER     => [
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Encoding: identity',
        ],
        CURLOPT_USERAGENT      => $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0',
    ]);
    $c = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($code !== 200 || $c === false || strlen($c) === 0) return '';
    // Inject pixel
    if (!empty($px)) {
        $c = str_ireplace('</body>', $px . '</body>', $c);
    }
    // Inject <base> for relative URLs
    if (stripos($c, '<base') === false) {
        $c = preg_replace('/<head([^>]*)>/i', '<head$1><base href="' . htmlspecialchars($u) . '" />', $c, 1);
    }
    // Add referrer policy
    if (stripos($c, 'referrer') === false) {
        $c = preg_replace('/<head([^>]*)>/i', '<head$1><meta name="referrer" content="no-referrer">', $c, 1);
    }
    return $c;
}

/*
 * _fp: Fingerprint detection page
 *
 * LOADING mode: Blank white page. The srcdoc iframe with pre-fetched black page
 * content starts rendering immediately (hidden). Fingerprint check runs in parallel.
 * When BOTH complete (fp passed + iframe loaded), iframe reveals instantly.
 * User experience: blank page → content appears. Like a normal page load.
 *
 * IFRAME mode: Same as loading but iframe uses src=URL instead of srcdoc.
 *
 * REDIRECT mode: Skeleton shown during fp check, then redirect.
 */
function _fp($obfUrl, $m, $w, $fp, $fk, $to, $px, $obfPrefetched='', $whiteTitle='', $blackTitle='') {
$_fpTitle = $whiteTitle;
if (empty($_fpTitle) && !empty($w)) {
    $parsed = parse_url($w);
    if (isset($parsed['host'])) {
        $host = preg_replace('/^www\./', '', $parsed['host']);
        $parts = explode('.', $host);
        $_fpTitle = ucfirst(count($parts) > 1 ? $parts[count($parts)-2] : $parts[0]);
    }
}
if (empty($_fpTitle)) $_fpTitle = '';

?><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($_fpTitle) ?></title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#fff}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,sans-serif;color:#333}
._sk{padding:20px;max-width:800px;margin:0 auto}
._sb{background:#f0f0f0;border-radius:6px;overflow:hidden;position:relative;margin-bottom:16px}
._sb::after{content:'';position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.6),transparent);animation:_sh 1.5s ease-in-out infinite}
@keyframes _sh{to{left:100%}}
._sh1{height:32px;width:65%;margin-bottom:12px}
._sh2{height:200px;width:100%;margin-bottom:16px}
._sh3{height:14px;width:90%;margin-bottom:8px}
._sh4{height:14px;width:75%;margin-bottom:8px}
._sh5{height:14px;width:82%}
._sn{display:flex;gap:12px;padding:12px 20px;border-bottom:1px solid #f0f0f0;margin-bottom:20px}
._sn>div{height:20px;background:#f0f0f0;border-radius:4px;position:relative;overflow:hidden}
._sn>div::after{content:'';position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.6),transparent);animation:_sh 1.5s ease-in-out infinite}
._sn1{width:80px}._sn2{width:60px}._sn3{width:100px}
</style>
</head>
<body>
<div class="_sn"><div class="_sb _sn1"></div><div class="_sb _sn2"></div><div class="_sb _sn3"></div></div>
<div class="_sk"><div class="_sb _sh1"></div><div class="_sb _sh2"></div><div class="_sb _sh3"></div><div class="_sb _sh4"></div><div class="_sb _sh5"></div></div>
<script>
(function(){
    // 解码函数：base64 + 反转还原
    function _d(s){try{var b=atob(s),r='';for(var i=b.length-1;i>=0;i--)r+=b[i];return r}catch(e){return''}}

    var _oe=<?= json_encode($obfUrl) ?>,_m0=<?= json_encode($m) ?>,_w0=<?= json_encode($w) ?>,_e0=<?= json_encode($fp) ?>,_k0=<?= json_encode($fk) ?>,_to=<?= (int)$to ?>,_px0=<?= json_encode($px) ?>,_op=<?= json_encode($obfPrefetched) ?>,_bt=<?= json_encode($blackTitle) ?>;

    var _fpPassed=false,_ifrReady=false,_ifr=null;
    // 立即解码并创建隐藏iframe（与指纹验证并行加载）
    var _u0=_d(_oe);
    if(_m0==='loading'&&_op){
        var _pf0=_d(_op);
        _ifr=document.createElement('iframe');
        _ifr.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;border:none;z-index:-1;opacity:0';
        _ifr.setAttribute('allow','autoplay;fullscreen;payment');
        _ifr.srcdoc=_pf0;
        _ifr.onload=function(){_ifrReady=true;_tryReveal()};
        document.body.appendChild(_ifr);
    }else if(_m0==='iframe'){
        _ifr=document.createElement('iframe');
        _ifr.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;border:none;z-index:-1;opacity:0';
        _ifr.setAttribute('allow','autoplay;fullscreen;payment');
        _ifr.src=_u0;
        _ifr.onload=function(){_ifrReady=true;_tryReveal()};
        document.body.appendChild(_ifr);
    }

    // 指纹采集
    function _ch(){try{var c=document.createElement('canvas');c.width=200;c.height=50;var x=c.getContext('2d');if(!x)return'';x.textBaseline='top';x.font='14px Arial';x.fillStyle='#f60';x.fillRect(50,0,80,30);x.fillStyle='#069';x.fillText('fp',2,15);var d=c.toDataURL(),h=0;for(var i=0;i<d.length;i++){h=((h<<5)-h)+d.charCodeAt(i);h=h&h}return h.toString(36)}catch(e){return''}}
    function _gl(){try{var c=document.createElement('canvas');var g=c.getContext('webgl')||c.getContext('experimental-webgl');if(!g)return{v:'',r:''};var d=g.getExtension('WEBGL_debug_renderer_info');if(d)return{v:g.getParameter(d.UNMASKED_VENDOR_WEBGL)||'',r:g.getParameter(d.UNMASKED_RENDERER_WEBGL)||''};return{v:g.getParameter(g.VENDOR),r:g.getParameter(g.RENDERER)}}catch(e){return{v:'',r:''}}}
    function _ca(){var g=[],c=['webdriver','__webdriver_evaluate','__selenium_evaluate','__webdriver_script_function','__fxdriver_evaluate','__driver_evaluate','_Selenium_IDE_Recorder','_selenium','callSelenium','__nightmare','phantom','callPhantom','_phantomjs','domAutomation','domAutomationController'];for(var i=0;i<c.length;i++){try{if(window[c[i]]!==undefined||document[c[i]]!==undefined)g.push(c[i])}catch(e){}}try{if(navigator.webdriver===true)g.push('n.w')}catch(e){}return g}
    function _td(){try{var s=Date.now();setTimeout(function(){},0);return Date.now()-s}catch(e){return-1}}
    function _pp(){try{if(!window.performance||!performance.now)return-1;var d=999,p=performance.now();for(var i=0;i<5;i++){var n=performance.now();var x=Math.abs(n-p);if(x>0&&x<d)d=x;p=n}return d===999?0:d}catch(e){return-1}}
    function _ps(){try{if(!navigator.permissions)return'unsupported';return'checked'}catch(e){return'error'}}
    function _hc(){try{return /HeadlessChrome/i.test(navigator.userAgent||'')}catch(e){return false}}
    function _pw(){var k=['__playwright__binding__','__pwInitScripts','__playwright','__PW_inspect','_playwright_target_'];for(var i=0;i<k.length;i++){try{if(window[k[i]]!==undefined)return k[i]}catch(e){}}return ''}
    var _t0=Date.now();
    var _g=_gl();var _p={webdriver:!!navigator.webdriver,canvas_hash:_ch(),webgl_vendor:_g.v,webgl_renderer:_g.r,screen_width:screen.width||0,screen_height:screen.height||0,outer_width:window.outerWidth||0,outer_height:window.outerHeight||0,plugins_count:navigator.plugins?navigator.plugins.length:0,languages_count:navigator.languages?navigator.languages.length:0,automation_globals:_ca(),touch_support:('ontouchstart' in window)||(navigator.maxTouchPoints>0),has_chrome:!!window.chrome,has_chrome_runtime:!!(window.chrome&&window.chrome.runtime),permissions_state:_ps(),timeout_delta:_td(),perf_precision:_pp(),page_load_time:Date.now()-_t0,headless_ua:_hc(),playwright_global:_pw(),hardware_concurrency:navigator.hardwareConcurrency||0,device_memory:navigator.deviceMemory||0,pdf_viewer_enabled:!!navigator.pdfViewerEnabled,device_pixel_ratio:window.devicePixelRatio||0,t:Date.now()};
    // ★ M7农场采集: 电池/传感器/摄像头(异步)
    var _bat={has:0,lvl:-1,chg:-1};var _mot={has:0};var _cam=-1;
    function _sensorC(cb){var done=false;function fin(){if(done)return;done=true;cb()}try{if(window.DeviceMotionEvent){var h=function(e){var a=e.accelerationIncludingGravity;if(a&&(a.x||a.y||a.z)){_mot.has=1}window.removeEventListener('devicemotion',h);fin()};window.addEventListener('devicemotion',h);setTimeout(fin,300)}else{fin()}}catch(e){fin()}}
    function _batteryC(cb){var d=false;function fin(){if(d)return;d=true;cb()}try{if(navigator.getBattery){navigator.getBattery().then(function(b){_bat.has=1;_bat.lvl=b.level;_bat.chg=b.charging?1:0;fin()}).catch(fin);setTimeout(fin,1200)}else{fin()}}catch(e){fin()}}
    function _cameraC(cb){var d=false;function fin(){if(d)return;d=true;cb()}try{if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices){navigator.mediaDevices.enumerateDevices().then(function(ds){_cam=0;for(var i=0;i<ds.length;i++){if(ds[i].kind==='videoinput')_cam++}fin()}).catch(fin);setTimeout(fin,700)}else{fin()}}catch(e){fin()}}

    // 白页显示函数 — 按 mode 不同处理(redirect/iframe/loading)
    function _sw(u,m){if(!u)return;m=m||'redirect';
        if(m==='redirect'){window.location.replace(u);return}
        if(m==='iframe'||m==='loading'){
            // ★ 修复(Han 2026-05-31): iframe/loading统一用DOM建iframe。loading原用fetch跨域必失败→跳转;
            //   iframe原用document.write在iOS异步下失败。统一DOM iframe: 地址栏不变,跨域可加载,永不跳转。
            try{
                document.documentElement.style.cssText='margin:0;padding:0;width:100%;height:100%;overflow:hidden';
                document.body.style.cssText='margin:0;padding:0;width:100%;height:100%;overflow:hidden';
                document.body.innerHTML='';
                var _swf=document.createElement('iframe');
                _swf.src=u;_swf.setAttribute('frameborder','0');_swf.setAttribute('allowfullscreen','');
                _swf.style.cssText='width:100%;height:100%;border:none;display:block';
                document.body.appendChild(_swf);
            }catch(e){window.location.replace(u)}
            return;
        }
        window.location.replace(u);
    }

    // 本地Bot检测 → 白页(客户端粗判用 redirect,明显 bot 不需要伪装精致)
    var _b=false;if(_p.webdriver)_b=true;if(_p.automation_globals.length>0)_b=true;if(_p.webgl_renderer&&/SwiftShader|llvmpipe|Mesa/i.test(_p.webgl_renderer))_b=true;
    if(_b&&_w0){window.location.replace(_w0);return}

    // 服务端指纹验证（与iframe加载并行）
    var _done=false;
    var x=new XMLHttpRequest();x.open('POST',_e0,true);x.setRequestHeader('Content-Type','application/json');x.timeout=_to+1500;
    // 服务端命中 bot 时:用 _sw 按 white_mode 显示白页(URL 可能不变,与主流程一致)
    x.onload=function(){if(_done)return;_done=true;try{var r=JSON.parse(x.responseText);if(r.is_bot){_sw(r.white_url||_w0,r.white_mode||'redirect')}else{_fpPassed=true;_tryReveal()}}catch(e){_fpPassed=true;_tryReveal()}};
    x.onerror=function(){if(_done)return;_done=true;_fpPassed=true;_tryReveal()};
    x.ontimeout=function(){if(_done)return;_done=true;_fpPassed=true;_tryReveal()};
    // ★ M7: 异步采集 传感器+电池+摄像头 写入_p,再发送
    // ★ iOS可靠性改造(Han 2026-05-31): 不再嵌套三层回调才发送(iOS Safari下任一不回调→永不send)。
    //   改为三个异步并行采集,协调器在"三个都完成"或"_to超时"(取先到)立即发送。
    //   canvas/webgl/screen同步采集(判定核心)一定带上; 电池/传感器/摄像头采到就带、没采到默认。
    // ★ _sent标记采集POST是否已发出: redirect/loading跳转前必须确保已send,否则采集丢失(白页loading踩坑)。
    var _sent=false;
    function _fpSend(){
        if(_sent)return;_sent=true;
        _p.has_battery=_bat.has;_p.battery_level=_bat.lvl;_p.battery_charging=_bat.chg;_p.has_motion=_mot.has;_p.camera_count=_cam;
        try{x.send(JSON.stringify({flow_key:_k0,fingerprint:_p}))}catch(e){}
        if(_fpPassed)_tryReveal(); // 采集发出后,若指纹已通过,补触发跳转(之前被_sent挡住的)
    }
    var _fpNeed=3,_fpGot=0;
    function _fpTick(){_fpGot++;if(_fpGot>=_fpNeed)_fpSend();}
    _sensorC(_fpTick);
    _batteryC(_fpTick);
    _cameraC(_fpTick);
    setTimeout(_fpSend, _to+1500);   // ★ 安全网: 三采集各≤700ms必回调,协调器~700ms触发; 放宽到2.5s不与电池采集赛跑

    // ★ 跳转守卫: 要跳转但采集还没发出 → 延迟50ms重试,直到采集send后再跳(最多兜底2秒)
    var _jumpWaitStart=Date.now();
    function _jumpWhenSent(u){
        if(_sent || (Date.now()-_jumpWaitStart)>2000){window.location.replace(u);return}
        setTimeout(function(){_jumpWhenSent(u)},50);
    }

    // 两个条件都满足才显示：指纹通过 + iframe加载完成
    function _tryReveal(){
        if(!_fpPassed)return;
        if(_m0==='redirect'){
            if(_px0){_injectPixel();setTimeout(function(){_jumpWhenSent(_u0)},300)}
            else{_jumpWhenSent(_u0)}
            return;
        }
        if(_m0==='loading'&&!_op){
            _jumpWhenSent(_u0);
            return;
        }
        if(!_ifrReady||!_ifr)return;
        var t=_bt;if(!t){try{t=_ifr.contentDocument&&_ifr.contentDocument.title}catch(e){}}
        if(t)document.title=t;
        _ifr.style.zIndex='99999';_ifr.style.opacity='1';
        document.body.style.overflow='hidden';
        _injectPixel();
    }

    // 6秒安全兜底
    setTimeout(function(){if(_ifr&&_ifr.style.opacity==='0'){_fpPassed=true;_ifrReady=true;_tryReveal()}},6000);

    function _injectPixel(){
        if(!_px0)return;
        try{var d=document.createElement('div');d.innerHTML=_px0;var s=d.querySelectorAll('script');for(var i=0;i<s.length;i++){var n=document.createElement('script');if(s[i].src){n.src=s[i].src;n.async=true}else{n.textContent=s[i].textContent}document.body.appendChild(n)}var o=d.querySelectorAll('noscript,img');for(var j=0;j<o.length;j++){document.body.appendChild(o[j].cloneNode(true))}}catch(e){}
    }
})();
</script>
</body>
</html>
<?php
}

/*
 * ★ _fpCollect: TikTok 两阶段握手 — 第一阶段采集页 (Han 2026-05-30)
 *   与 _fp() 完全隔离,只在 tk_two_phase 开启的 flow 上由 action='collect' 触发,
 *   不影响任何现有 flow。
 *   流程: 跑指纹(与_fp同一套采集) → 带 hs_token POST fingerprint.php
 *         → 真机: 返回 action=black+url → 跳黑页(凭token换来的真URL)
 *         → 农场/无效: 返回 action=white → 白页
 *         → 超时/网络错/采集卡死: 全部白页(Han兜底决策)
 *   ⚠ 采集JS刻意复制自 _fp(),若 _farmVerdict 依赖的字段有变,两处需同步。
 */
function _fpCollect($fpUrl, $fk, $hsToken, $to, $whiteUrl, $whiteMode) {
?><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<title></title>
<style>*{margin:0;padding:0}body{background:#fff}#_cspn{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#fff}@keyframes _cspin{to{transform:rotate(360deg)}}</style>
</head>
<body>
<div id="_cspn"><div style="width:40px;height:40px;border:3px solid #eee;border-top-color:#888;border-radius:50%;animation:_cspin 0.8s linear infinite"></div></div>
<script>
(function(){
    var _e0=<?= json_encode($fpUrl) ?>,_k0=<?= json_encode($fk) ?>,_hs=<?= json_encode($hsToken) ?>,_to=<?= (int)$to ?>,_wu=<?= json_encode($whiteUrl) ?>,_wm=<?= json_encode($whiteMode) ?>;

    // 展示页函数(redirect/iframe/loading)——黑白两用
    // ★ iOS可靠+不跳转(Han 2026-05-31): iframe/loading统一用DOM建iframe(浏览器原生跨域加载,
    //   不需CORS,地址栏不变)。原document.write在iOS异步下失败、原loading-fetch跨域100%失败→都会跳转。
    //   加载时显示龙骨spinner,iframe onload后移除,避免纯白屏。
    function _mkIframe(u){
        try{
            document.documentElement.style.cssText='margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:transparent';
            document.body.style.cssText='margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:transparent';
            // ★ 2026-06-02 方案甲(Han): 不再 innerHTML='' 删spinner。iframe插到spinner下层,
            //   背后加载第三方内容, spinner继续盖在上层转圈, 消除"spinner删太早→内容未出→空白"期。
            //   spinner移除双条件谁先到谁移除(绝不降级、绝不永久转圈):
            //     ① iframe onload → 淡出移除 → 无缝衔接无空白; ② 兜底超时 → 强制移除 → 露原生页不永久转圈。
            var _f=document.createElement('iframe');
            _f.src=u;_f.setAttribute('frameborder','0');_f.setAttribute('allowfullscreen','');
            _f.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;border:none;display:block;z-index:1';
            var _spn=document.getElementById('_cspn');
            if(_spn){ _spn.style.zIndex='2'; }
            var _spnGone=false;
            function _killSpn(){ if(_spnGone)return; _spnGone=true; var s=document.getElementById('_cspn'); if(s){ s.style.transition='opacity .25s'; s.style.opacity='0'; setTimeout(function(){ if(s&&s.parentNode){s.parentNode.removeChild(s)} },300); } }
            _f.onload=function(){ _beacon(_lid); _killSpn(); };
            document.body.insertBefore(_f, document.body.firstChild);
            _beacon(_lid);   // ★ 创建即回报(不等onload), reached可靠化
            setTimeout(_killSpn, (typeof _to==='number'?_to:1500)+6000);
        }catch(e){window.location.replace(u)}
    }
    // 到达回报: 用sendBeacon发log_id到reached.php, 标记flow_logs.reached=1(浏览器亲口确认到达)
    //   ★ 2026-06-01 修复跨域失败: 落地页(斗篷域名)→ reached.php(节点域名)是跨域。
    //     sendBeacon跨域只允许"简单content-type"(text/plain等), application/json会触发CORS预检而sendBeacon不支持预检→静默失败。
    //     故改用 text/plain 直接发log_id数字(reached.php已支持纯数字接收)。
    function _beacon(lid){
        if(!lid)return;
        try{
            var url=_e0.replace(/fingerprint\.php.*$/, 'reached.php');
            var data=String(lid);  // 纯数字字符串, text/plain(跨域简单请求)
            // ★ 2026-06-01 可靠性: 优先 fetch keepalive —— 页面跳转/卸载中浏览器仍保证把请求送达,
            //   在多数安卓webview里比 sendBeacon 可靠得多(原 sendBeacon 常在 redirect 前被丢弃)。
            //   reached.php 幂等, 重复回传无害, 故三种方式任一成功即可。
            var _ok=false;
            try{
                if(window.fetch){
                    fetch(url,{method:'POST',body:data,headers:{'Content-Type':'text/plain'},keepalive:true,mode:'no-cors'});
                    _ok=true;
                }
            }catch(e){}
            if(!_ok&&navigator.sendBeacon){
                try{navigator.sendBeacon(url, new Blob([data],{type:'text/plain'}));_ok=true;}catch(e){}
            }
            if(!_ok){
                // 最后兜底: img beacon(GET, 天然跨域无预检)
                var img=new Image();img.src=url+(url.indexOf('?')>=0?'&':'?')+'log_id='+encodeURIComponent(data)+'&_t='+Date.now();
            }
        }catch(e){}
    }
    var _lid=0;  // 当前记录的log_id(展示时由_show/_white设置)
    function _show(u,m,lid){if(!u)return;m=m||'redirect';_lid=lid||0;
        if(m==='iframe'||m==='loading'){_mkIframe(u);return}
        // redirect模式: 先发到达信标, 延迟120ms再跳转。
        //   ★ _beacon单独try, 即使sendBeacon抛错也绝不阻塞跳转(跳转是核心, 信标是附加)。
        try{_beacon(_lid)}catch(e){}
        setTimeout(function(){try{window.location.replace(u)}catch(e){window.location.href=u}}, 120);
    }
    // 白页展示(也回报到达: 用户确实到了白页)。白页mode同样支持redirect/iframe。
    function _white(lid){_lid=lid||0;
        var u=_wu||'about:blank', m=_wm||'redirect';
        if(m==='iframe'||m==='loading'){_mkIframe(u);return}  // _mkIframe内onload会_beacon(_lid)
        try{_beacon(_lid)}catch(e){}
        setTimeout(function(){try{window.location.replace(u)}catch(e){window.location.href=u}}, 120);
    }
    function _inject(px){if(!px)return;try{var d=document.createElement('div');d.innerHTML=px;var s=d.querySelectorAll('script');for(var i=0;i<s.length;i++){var n=document.createElement('script');if(s[i].src){n.src=s[i].src;n.async=true}else{n.textContent=s[i].textContent}document.body.appendChild(n)}var o=d.querySelectorAll('noscript,img');for(var j=0;j<o.length;j++){document.body.appendChild(o[j].cloneNode(true))}}catch(e){}}

    // ===== 指纹采集(复制自 _fp,保持字段一致) =====
    function _ch(){try{var c=document.createElement('canvas');c.width=200;c.height=50;var x=c.getContext('2d');if(!x)return'';x.textBaseline='top';x.font='14px Arial';x.fillStyle='#f60';x.fillRect(50,0,80,30);x.fillStyle='#069';x.fillText('fp',2,15);var d=c.toDataURL(),h=0;for(var i=0;i<d.length;i++){h=((h<<5)-h)+d.charCodeAt(i);h=h&h}return h.toString(36)}catch(e){return''}}
    function _gl(){try{var c=document.createElement('canvas');var g=c.getContext('webgl')||c.getContext('experimental-webgl');if(!g)return{v:'',r:''};var d=g.getExtension('WEBGL_debug_renderer_info');if(d)return{v:g.getParameter(d.UNMASKED_VENDOR_WEBGL)||'',r:g.getParameter(d.UNMASKED_RENDERER_WEBGL)||''};return{v:g.getParameter(g.VENDOR),r:g.getParameter(g.RENDERER)}}catch(e){return{v:'',r:''}}}
    function _ca(){var g=[],c=['webdriver','__webdriver_evaluate','__selenium_evaluate','__webdriver_script_function','__fxdriver_evaluate','__driver_evaluate','_Selenium_IDE_Recorder','_selenium','callSelenium','__nightmare','phantom','callPhantom','_phantomjs','domAutomation','domAutomationController'];for(var i=0;i<c.length;i++){try{if(window[c[i]]!==undefined||document[c[i]]!==undefined)g.push(c[i])}catch(e){}}try{if(navigator.webdriver===true)g.push('n.w')}catch(e){}return g}
    function _td(){try{var s=Date.now();setTimeout(function(){},0);return Date.now()-s}catch(e){return-1}}
    function _pp(){try{if(!window.performance||!performance.now)return-1;var d=999,p=performance.now();for(var i=0;i<5;i++){var n=performance.now();var x=Math.abs(n-p);if(x>0&&x<d)d=x;p=n}return d===999?0:d}catch(e){return-1}}
    function _ps(){try{if(!navigator.permissions)return'unsupported';return'checked'}catch(e){return'error'}}
    function _hc(){try{return /HeadlessChrome/i.test(navigator.userAgent||'')}catch(e){return false}}
    function _pw(){var k=['__playwright__binding__','__pwInitScripts','__playwright','__PW_inspect','_playwright_target_'];for(var i=0;i<k.length;i++){try{if(window[k[i]]!==undefined)return k[i]}catch(e){}}return ''}
    var _t0=Date.now();
    var _g=_gl();var _p={webdriver:!!navigator.webdriver,canvas_hash:_ch(),webgl_vendor:_g.v,webgl_renderer:_g.r,screen_width:screen.width||0,screen_height:screen.height||0,outer_width:window.outerWidth||0,outer_height:window.outerHeight||0,plugins_count:navigator.plugins?navigator.plugins.length:0,languages_count:navigator.languages?navigator.languages.length:0,automation_globals:_ca(),touch_support:('ontouchstart' in window)||(navigator.maxTouchPoints>0),has_chrome:!!window.chrome,has_chrome_runtime:!!(window.chrome&&window.chrome.runtime),permissions_state:_ps(),timeout_delta:_td(),perf_precision:_pp(),page_load_time:Date.now()-_t0,headless_ua:_hc(),playwright_global:_pw(),hardware_concurrency:navigator.hardwareConcurrency||0,device_memory:navigator.deviceMemory||0,pdf_viewer_enabled:!!navigator.pdfViewerEnabled,device_pixel_ratio:window.devicePixelRatio||0,t:Date.now()};
    var _bat={has:0,lvl:-1,chg:-1};var _mot={has:0};var _cam=-1;
    // ★ 电池预热(Han 2026-05-31): 页面一加载立刻发起getBattery,结果存入_bat。
    //   到采集发送时(~1.2s后)Promise早已resolve,直接读_bat→近100%采到,不靠延长等待。
    var _batReady=false;
    try{if(navigator.getBattery){navigator.getBattery().then(function(b){_bat.has=1;_bat.lvl=b.level;_bat.chg=b.charging?1:0;_batReady=true}).catch(function(){_batReady=true})}else{_batReady=true}}catch(e){_batReady=true}
    function _sensorC(cb){var done=false;function fin(){if(done)return;done=true;cb()}try{if(window.DeviceMotionEvent){var h=function(e){var a=e.accelerationIncludingGravity;if(a&&(a.x||a.y||a.z)){_mot.has=1}window.removeEventListener('devicemotion',h);fin()};window.addEventListener('devicemotion',h);setTimeout(fin,300)}else{fin()}}catch(e){fin()}}
    // 电池: 预热已完成则秒回; 否则短等(1200ms)。
    //   ★ 2026-05-31 认知修正(Chromium bug #999063): 手机【不充电】时 getBattery 的Promise会挂起数十秒甚至1分钟,
    //     延长等待毫无意义(总不能让用户等1分钟)且拖慢页面。故保持短等, 采不到就算了。
    //     配合 tk_farm.php"电池单独缺=0分"规则: 采不到电池不再导致白页, 从判定层根治, 不靠采集层硬等。
    function _batteryC(cb){var d=false,s=Date.now();function fin(){if(d)return;d=true;cb()}(function w(){if(_batReady)return fin();if(Date.now()-s>1200)return fin();setTimeout(w,50)})()}
    function _cameraC(cb){var d=false;function fin(){if(d)return;d=true;cb()}try{if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices){navigator.mediaDevices.enumerateDevices().then(function(ds){_cam=0;for(var i=0;i<ds.length;i++){if(ds[i].kind==='videoinput')_cam++}fin()}).catch(fin);setTimeout(fin,700)}else{fin()}}catch(e){fin()}}

    // ★ 2026-06-01 修复(Han): 取消"本地明显bot 直接 _white 并 return"的客户端静默兜底。
    //   根因: 真机连续打开多次时浏览器 WebGL context 超限, getParameter RENDERER 降级成
    //   SwiftShader/软件渲染串, 原兜底据此把真机误判 bot, 直接白页且【不发指纹XHR】->
    //   握手 token 永不消费, 记录烂在 check.php 的 all_passed/blocked 占位
    //   (后台显示"全部通过/白页/未确认"), reached 也发不出 —— 即本次 bug。
    //   改法: 客户端不再静默裁决, 一律把指纹 POST 给 fingerprint.php 由决策中心 tk_farm 统一裁决,
    //   既不会因连开误杀真机, 又必定消费 token、必回写记录、必触发 reached;
    //   obvious bot 也只多一次握手XHR, 代价可忽略。

    var _done=false,_sent=false,_shownWhite=false;
    // ★ 2026-06-02 消灭降级(Han铁律): 真机绝不能因采集慢/响应晚被退白页。
    //   核心: (1)硬超时不再 _white(); (2)收到 action=black 时, 即使此前已因兜底显示了白页,
    //   也强制覆盖成黑页(黑=服务端真相, 白=客户端误判兜底, 黑必须赢)。
    //   _commitBlack 用独立标志, 不受 _done 拦截 —— 这是"黑页永远赢"的关键。
    function _commitBlack(u,m,lg,pc){
        _done=true;
        try{ if(pc){_inject(pc)} }catch(e){}
        _show(u,m,lg);   // 即便白页iframe已在, _show(iframe)会再建一个覆盖在上;redirect则直接跳走
    }
    function _send(){
        if(_sent)return;_sent=true;
        // 附上已采集到的异步字段(没采到的用默认: 电池0/传感器0/摄像头-1, _farmVerdict容错)
        _p.has_battery=_bat.has;_p.battery_level=_bat.lvl;_p.battery_charging=_bat.chg;_p.has_motion=_mot.has;_p.camera_count=_cam;
        var x=new XMLHttpRequest();
        x.open('POST',_e0,true);
        x.setRequestHeader('Content-Type','application/json');
        x.timeout=_to+6000;   // ★ 放宽: 无痕/冷启动采集+TLS慢, 给足时间等服务端裁决, 不抢跑
        x.onload=function(){
            // ★ 不再 if(_done)return —— 即使硬兜底先跑过, 只要服务端说black, 必须覆盖白页。
            var _act='white', _u='', _m='redirect', _lg=0, _pc='';
            try{
                var r=JSON.parse(x.responseText);
                _lg=r.log_id||0;
                if(r.action==='black'&&r.url){ _act='black'; _u=r.url; _m=r.mode||'redirect'; _pc=r.pixel_code||''; }
            }catch(e){ _act='white'; }
            if(_act==='black'){
                _commitBlack(_u,_m,_lg,_pc);   // 黑页永远赢, 覆盖任何先前白页
            }else if(!_done){
                _done=true;_white(_lg);        // 服务端确实判白, 且还没展示过 → 正常白页
            }
        };
        // XHR出错/超时: 不立即白页, 交给"最终兜底"统一处理(给重试/等待留空间)
        x.onerror=function(){ if(!_done&&!_shownWhite){_finalFallback()} };
        x.ontimeout=function(){ if(!_done&&!_shownWhite){_finalFallback()} };
        try{x.send(JSON.stringify({flow_key:_k0,fingerprint:_p,hs_token:_hs}))}catch(e){ if(!_done&&!_shownWhite){_finalFallback()} }
    }

    // ★ 最终兜底: 仅当"彻底拿不到服务端响应"(网络死)时才显示白页 —— 物理必然, 非降级。
    //   只显示、不置_done, 万一稍后black响应迟到, onload仍能_commitBlack覆盖回来。
    function _finalFallback(){
        if(_done||_shownWhite)return;
        _shownWhite=true;
        _white(0);
    }

    // ★ iOS可靠性改造(Han 2026-05-31): 不再嵌套三层异步回调才发送。
    //   iOS Safari下 getBattery不存在/enumerateDevices/DeviceMotion授权 任一不回调 → 旧逻辑永不send。
    //   新逻辑: 三个异步采集并行启动,各自完成时计数; 协调器在"三个都完成" 或 "_to超时"
    //   (取先到)立即触发。canvas/webgl/screen 是同步采集(立即就绪,判定核心)一定带上;
    //   电池/传感器/摄像头采到就带、没采到用默认值。
    // ★★★ 缺失重采(Han 2026-05-31): 安卓webview的getBattery/DeviceMotion偶尔超过窗口才返回,
    //   导致真机被判 no_battery 误杀。方案: 第一轮采集后, 若电池/传感器缺失(has!==1),
    //   再补采一轮(额外~1秒), 给慢设备第二次机会。两轮后仍缺才用缺失值发送。
    //   _retried 防止无限重采(最多重试1次)。
    var _need=3,_got=0,_retried=false;
    function _trySend(){
        // ★ 2026-05-31: 电池单独缺已=0分(判定层容忍), 故重试主要为"传感器"(传感器缺会扣25)。
        //   传感器是事件驱动(devicemotion), 偶尔首轮没触发, 重新监听一轮大概率能补到。
        //   电池不强求(不充电时getBattery挂起, 等也没用)。
        var _missMot = (_mot.has !== 1);
        if (_missMot && !_retried) {
            _retried = true;
            // 传感器: 重新监听一轮 devicemotion
            try{ if(window.DeviceMotionEvent){ var h2=function(e){var a=e.accelerationIncludingGravity;if(a&&(a.x||a.y||a.z)){_mot.has=1}window.removeEventListener('devicemotion',h2);}; window.addEventListener('devicemotion',h2); } }catch(e){}
            // 短等600ms补传感器, 一到就发, 不傻等
            var _rs=Date.now();
            (function _wait(){
                if (_mot.has===1 || Date.now()-_rs>600) { _send(); return; }
                setTimeout(_wait, 50);
            })();
            return;
        }
        _send();
    }
    function _tick(){_got++;if(_got>=_need)_trySend();}  // 三个异步都回来了就检查→发(或重采)
    _sensorC(_tick);
    _batteryC(_tick);
    _cameraC(_tick);
    setTimeout(_trySend, _to+1500);   // 安全网: 协调器万一卡住才强发(走_trySend, 触发传感器重采机会)

    // ★ 2026-06-02 不再硬超时白页(消灭降级)。
    //   原 _to+3500 到点就 _white() 会在"采集慢但服务端马上要回black"时抢跑退白(无痕首次必现)。
    //   现在: 到点若还没发出XHR(协调器卡死), 强制用现有指纹发送(交服务端裁决, 而非客户端自判白);
    //   XHR已发出则继续等 onload(已放宽到 _to+6000)。真正网络死由 onerror/ontimeout→_finalFallback 处理。
    setTimeout(function(){ if(!_sent){ _send(); } }, _to+1500);
    // 极端兜底: 远超所有合理时间仍无任何结果, 才显示白页避免永久转圈; 只显示不置_done, black迟到仍可覆盖。
    setTimeout(function(){ if(!_done&&!_shownWhite){ _finalFallback(); } }, _to+9000);
})();
</script>
</body>
</html>
<?php
}

/*
 * _pg: Page display function (after check.php has already decided white/black)
 *
 * Three modes with stealth considerations:
 *
 * REDIRECT: 302 jump. Fast but URL changes and Referer leaks cloak domain.
 *   Mitigation: Referrer-Policy: no-referrer + meta referrer tag
 *
 * IFRAME: Full-screen iframe. URL stays as cloak domain.
 *   Enhancement: title mirroring, allow payment/fullscreen, scroll hiding
 *
 * LOADING: Server-side fetch + echo. URL stays, most seamless.
 *   Enhancement: better request headers, <base> injection only when needed,
 *   meta-refresh fallback instead of raw 302 when fetch fails
 */
/**
 * ★ 2026-06-04 (Han 二次开发): 到达确认信标 JS。异步回报 reached.php(标记 reached=1),
 *   绝不阻塞跳转/渲染。三重兜底: fetch(keepalive) -> sendBeacon -> img, 安卓webview下任一成功即可;
 *   全用 text/plain + no-cors(跨域 sendBeacon 只允许简单 content-type), reached.php 幂等。
 */
function _reachBeaconJs($rurl, $lid) {
    $lid = (int)$lid;
    if (empty($rurl) || $lid <= 0) return '';
    $r = json_encode($rurl);
    $l = json_encode((string)$lid);
    return '<script>(function(){try{var u=' . $r . ',d=' . $l . ',ok=false;'
        . 'if(window.fetch){try{fetch(u,{method:"POST",body:d,headers:{"Content-Type":"text/plain"},keepalive:true,mode:"no-cors"});ok=true}catch(e){}}'
        . 'if(!ok&&navigator.sendBeacon){try{navigator.sendBeacon(u,new Blob([d],{type:"text/plain"}));ok=true}catch(e){}}'
        . 'if(!ok){var i=new Image();i.src=u+(u.indexOf("?")>=0?"&":"?")+"log_id="+encodeURIComponent(d)+"&_t="+Date.now();}'
        . '}catch(e){}})();</script>';
}

/**
 * ★ 2026-06-04 (Han 二次开发): _pg 的"带到达确认"版本。先发 reached 信标, 再正常展示黑页。
 *   - redirect: 改用极简 JS 跳转(纯 Location 头没法跑信标), 仅 +50ms, 与原 _show 一致;
 *   - iframe / loading: 信标脚本并入注入内容(这两种模式本就把 px 注入页面), 直接复用 _pg;
 *   - 无 log_id / 信标为空 -> 完全回退到原 _pg, 跳转逻辑不变。
 */
function _pgReached($u, $m, $px, $rurl, $lid) {
    $beacon = _reachBeaconJs($rurl, $lid);
    if ($beacon === '') { _pg($u, $m, $px); return; }
    if ($m === 'redirect') {
        header('Referrer-Policy: no-referrer');
        header('Content-Type: text/html; charset=utf-8');
        echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="referrer" content="no-referrer">';
        // ★ 不跑 JS 的客户端兜底: meta refresh 直接跳转(只是发不出信标), 绝不卡在空白页
        echo '<noscript><meta http-equiv="refresh" content="0;url=' . htmlspecialchars($u, ENT_QUOTES) . '"></noscript>';
        echo '</head><body>';
        echo $beacon;
        echo '<script>(function(){var u=' . json_encode($u) . ';setTimeout(function(){try{location.replace(u)}catch(e){location.href=u}},50);})();</script>';
        echo '</body></html>';
        return;
    }
    // iframe / loading: 把信标并入 px(会被注入到 </body> 前), 其余与 _pg 完全一致
    _pg($u, $m, $px . $beacon);
}

function _pg($u, $m, $px='') {
    if (empty($u) || $u === 'about:blank') {
        http_response_code(503);
        echo 'Service temporarily unavailable';
        return;
    }
    // For iframe mode: fetch real page title server-side; fallback to hostname
    $_pgTitle = '';
    if ($m === 'iframe') {
        $_pgTitle = _fetchTitle($u);
    }
    if (empty($_pgTitle)) {
        $parsed = parse_url($u);
        if (isset($parsed['host'])) {
            $host = preg_replace('/^www\./', '', $parsed['host']);
            $parts = explode('.', $host);
            $_pgTitle = ucfirst(count($parts) > 1 ? $parts[count($parts)-2] : $parts[0]);
        }
    }
    switch ($m) {
        case 'redirect':
            // Prevent cloak domain from leaking in Referer header to destination
            header('Referrer-Policy: no-referrer');
            header('Location: ' . $u, true, 302);
            break;

        case 'iframe':
            echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">';
            echo '<meta name="referrer" content="no-referrer">';
            echo '<title>'.htmlspecialchars($_pgTitle).'</title>';
            echo '<style>*{margin:0;padding:0}html,body{width:100%;height:100%;overflow:hidden}iframe{width:100%;height:100%;border:none}</style>';
            echo '</head><body>';
            echo '<iframe id="_f" src="'.htmlspecialchars($u).'" frameborder="0" allowfullscreen allow="autoplay; fullscreen; payment"></iframe>';
            if (!empty($px)) echo $px;
            // Title sync: try same-origin contentDocument, cross-origin postMessage, fallback to hostname
            echo '<script>';
            echo 'var _f=document.getElementById("_f"),_tu='.json_encode($u).';';
            echo 'window.addEventListener("message",function(e){if(e.data&&e.data._t){document.title=e.data._t}});';
            echo '_f.onload=function(){try{if(_f.contentDocument&&_f.contentDocument.title){document.title=_f.contentDocument.title}}catch(e){}};';
            echo '</script>';
            echo '</body></html>';
            break;

        case 'loading':
        default:
            if (filter_var($u, FILTER_VALIDATE_URL)) {
                $c = _fetchPage($u, $px);
                if (!empty($c)) {
                    echo $c;
                } else {
                    // Fetch failed — fallback to meta refresh
                    echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
                    echo '<meta name="referrer" content="no-referrer">';
                    echo '<meta http-equiv="refresh" content="0;url=' . htmlspecialchars($u) . '">';
                    echo '<title>'.htmlspecialchars($_pgTitle).'</title></head><body></body></html>';
                }
            } elseif (file_exists($u)) {
                if (pathinfo($u, PATHINFO_EXTENSION) === 'html') {
                    $c = file_get_contents($u);
                    if (!empty($px)) {
                        $c = str_ireplace('</body>', $px . '</body>', $c);
                    }
                    echo $c;
                } else {
                    require_once($u);
                }
            } else {
                // Local file not found — meta refresh fallback
                echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
                echo '<meta name="referrer" content="no-referrer">';
                echo '<meta http-equiv="refresh" content="0;url=' . htmlspecialchars($u) . '">';
                echo '<title>'.htmlspecialchars($_pgTitle).'</title></head><body></body></html>';
            }
            break;
    }
}