Files
TankIC/3DTank/Start-LocalAndRelay.ps1
T
TermiNexus ae4d4ee055 fix(mirror): ws 抓包开关改为 serve.js --ws-hook(不碰 URL),避免 hash 破坏游戏路由
- 实测:URL 里任何 hash(包括 #hook/#nohook)都会让游戏进不去战场(游戏用 location.hash 做路由/状态),所以弃用 hash 开关

- serve.js --ws-hook:服务 HTML 时注入 /ws_hook.js;Start-LocalAndRelay.ps1 加 -Hook 开关;ws_hook.js 支持 localStorage.hornetWsHook=0 临时关闭

- index.html(本地)移除静态 ws_hook 引入
2026-10-11 21:43:12 +08:00

189 lines
9.8 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
一键启动 3DTank 本地镜像(serve.js)+ 中继(wss_server.js relay 模式)
.DESCRIPTION
- 本地镜像:把官方 web 客户端在 http://localhost:<MirrorPort>/ 跑起来
· web/ 提供首页与镜像资源;/play/static/js/** 直接挂 3dtank.com 的切割版 JS
· /config.xml 优先用 3DTank/local/config.xml(指向本地中继 9091),否则代理真实 config
- 中继:客户端 → ws://localhost:<RelayPort>/ → 真实服务器(抓包落盘 + 实时解密/解析)
· 产物目录:3DTank/packets/<label>/session_00N/
- 依赖:中继需要 ws(Node 自带的是客户端 WebSocket,服务端不够用),脚本会自动装
.EXAMPLE
// 最省事:装好依赖、起两个服务、打开浏览器
powershell -ExecutionPolicy Bypass -File .\3DTank\Start-LocalAndRelay.ps1 -OpenBrowser
.EXAMPLE
// 资源缺失(页面 404 很多)时先镜像资源再起(首次约 70MB)
.\Start-LocalAndRelay.ps1 -FetchAssets -OpenBrowser
.EXAMPLE
// 端口被上次残留占用时,先杀掉再起
.\Start-LocalAndRelay.ps1 -KillExisting -OpenBrowser
#>
[CmdletBinding()]
param(
[int]$MirrorPort = 8080,
[int]$RelayPort = 9091,
[string]$Upstream = 'wss://c1.3dtank.com:9090/',
[string]$Label = (Get-Date -Format 'yyyyMMdd-HHmmss'),
[switch]$FetchAssets, # 先跑 fetch_assets.js 镜像资源(首次或资源缺失时用)
[switch]$KillExisting, # 端口被占用时,先结束占用进程
[switch]$OpenBrowser, # 启动后打开 http://localhost:<MirrorPort>/
[switch]$WithRelay, # 启动本地中继(默认**不启动**:页面直连真实服务器 wss://c1.3dtank.com:9090/)
[switch]$Hook, # 本地镜像:注入页面内 ws 抓包(不用改 URL;游戏用 hash 做路由,加 hash 会进不去战场)
[int]$DumpEvery = 1, # 中继:每 N 帧写一个 .bin(默认 1 = 每帧都写;0 = 不写抓包文件)
[switch]$NoLog, # 中继:不写 log.txt(默认写)
[switch]$NoHex, # 中继:log.txt 不写十六进制(日志体积大幅减小)
[switch]$Quiet # 中继:只写 log.txt,不刷控制台
)
$ErrorActionPreference = 'Stop'
$Project = $PSScriptRoot # 3DTank/
$Tools = Join-Path $Project 'tools'
$PacketDir = Join-Path $Project ("packets\{0}" -f $Label)
# ---------- 小工具 ----------
function Get-CmdPath([string]$Name) {
$c = Get-Command $Name -ErrorAction SilentlyContinue
if (-not $c) { throw "找不到命令 '$Name',请先安装 Node.js(自带 npm)并加入 PATH" }
return $c.Source
}
function Test-PortFree([int]$Port) {
$client = New-Object System.Net.Sockets.TcpClient
try {
$client.Connect('127.0.0.1', $Port)
return $false # 连上了 = 已被占用
} catch {
return $true # 连不上 = 空闲
} finally {
try { $client.Close() } catch {}
}
}
function Get-PortPid([int]$Port) {
try {
return (Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction Stop |
Select-Object -First 1 -ExpandProperty OwningProcess)
} catch { return $null }
}
function Assert-PortReady([int]$Port, [string]$What) {
if (Test-PortFree $Port) { return }
if ($KillExisting) {
# 注意:$pid 是 PowerShell 只读自动变量(当前进程),不能用来存占用者 PID
$OwnerPid = Get-PortPid $Port
if ($OwnerPid) {
Write-Host ("[端口] {0} 被 PID={1} 占用,按 -KillExisting 结束它" -f $Port, $OwnerPid) -ForegroundColor Yellow
Stop-Process -Id $OwnerPid -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 500
}
}
if (-not (Test-PortFree $Port)) { throw "端口 $Port 已被占用($What)。用 -KillExisting 或换端口" }
}
function Test-TcpOpen([string]$HostName, [int]$Port, [int]$TimeoutMs = 2500) {
$client = New-Object System.Net.Sockets.TcpClient
try {
$task = $client.ConnectAsync($HostName, $Port)
if (-not $task.Wait($TimeoutMs)) { return $false }
return $client.Connected
} catch {
return $false
} finally {
try { $client.Close() } catch {}
}
}
# ---------- 0) 环境 ----------
$NodeExe = Get-CmdPath 'node'
$NpmExe = Get-CmdPath 'npm'
Write-Host ("[环境] node={0} npm={1}" -f (& $NodeExe -v), (& $NpmExe -v)) -ForegroundColor DarkGray
# ---------- 1) 依赖:中继需要 ws(服务端) ----------
$WsPackage = Join-Path $Tools 'node_modules\ws\package.json'
if (-not (Test-Path $WsPackage)) {
Write-Host '[依赖] 安装 ws 到 3DTank/tools ...' -ForegroundColor Cyan
Push-Location $Tools
try { & $NpmExe install --no-save ws | Out-Host } finally { Pop-Location }
}
if (-not (Test-Path $WsPackage)) { throw 'ws 安装失败(检查网络/代理)' }
# ---------- 2) 端口 ----------
Assert-PortReady $RelayPort '中继 wss_server.js'
Assert-PortReady $MirrorPort '本地镜像 serve.js'
# ---------- 3) 可选:镜像资源 ----------
if ($FetchAssets) {
Write-Host '[资源] fetch_assets.js 镜像资源(首次约 70MB,请耐心等)...' -ForegroundColor Cyan
Push-Location $Tools
try { & $NodeExe fetch_assets.js --concurrency 10 | Out-Host } finally { Pop-Location }
}
# ---------- 3.5) 生成单包(unpacked.pack.json):126 个小文件 → 1 个包 ----------
$PackScript = Join-Path (Split-Path -Parent $Project) 'tools\pack_unpacked.js'
if (Test-Path $PackScript) {
Write-Host '[打包] pack_unpacked.js 生成单包(unpacked.pack.json)...' -ForegroundColor Cyan
try { & $NodeExe $PackScript | Out-Host } catch { Write-Warning ('打包失败: ' + $_.Exception.Message) }
} else {
Write-Warning ('未找到 ' + $PackScript + ',跳过单包(将逐文件加载)')
}
# ---------- 4) 中继(可选;默认不启动 —— 页面直连真实服务器) ----------
$RelayProc = $null
if ($WithRelay) {
Write-Host ("[中继] wss_server.js relay ws://localhost:{0}/ -> {1}" -f $RelayPort, $Upstream) -ForegroundColor Green
$RelayArgs = @('wss_server.js', '--port', "$RelayPort", '--upstream', $Upstream, '--label', $Label, '--dump-every', "$DumpEvery")
if (-not $NoLog) { $RelayArgs += @('--log', '1') }
if ($NoHex) { $RelayArgs += @('--no-hex', '1') }
if ($Quiet) { $RelayArgs += @('--quiet', '1') }
# 用 cmd /k 启动:进程退出(含崩溃)后窗口不关闭,堆栈能直接看到("崩溃后 pause")
$RelayProc = Start-Process -FilePath 'cmd.exe' `
-ArgumentList (@('/k', 'node') + $RelayArgs) `
-WorkingDirectory $Tools -PassThru
} else {
Write-Host '[中继] 已跳过(默认直连真实服务器;要中继/抓包请加 -WithRelay)' -ForegroundColor DarkGray
}
# ---------- 5) 启动本地镜像 ----------
# 默认加 --real-config:/config.xml 取真实站点,游戏因此直连 wss://c1.3dtank.com:9090/
# 走中继(-WithRelay)时不加它,改用 local/config.xml 把客户端指向本机 127.0.0.1:<RelayPort>
$MirrorArgs = @('serve.js', '--port', "$MirrorPort")
if (-not $WithRelay) { $MirrorArgs += '--real-config' }
if ($Hook) { $MirrorArgs += '--ws-hook' }
Write-Host ("[镜像] serve.js http://localhost:{0}/ {1}{2}" -f $MirrorPort, ($(if ($WithRelay) {'(config → 本地中继)'} else {'(config → 真实服务器,直连)'})), ($(if ($Hook) {' + ws 抓包'} else {''}))) -ForegroundColor Green
$MirrorProc = Start-Process -FilePath 'cmd.exe' `
-ArgumentList (@('/k', 'node') + $MirrorArgs) `
-WorkingDirectory $Tools -PassThru
# ---------- 6) 健康检查 ----------
Start-Sleep -Milliseconds 1200
$RelayOk = if ($WithRelay) { Test-TcpOpen '127.0.0.1' $RelayPort } else { $null }
$MirrorOk = Test-TcpOpen '127.0.0.1' $MirrorPort
Write-Host ''
Write-Host '================= 已启动 =================' -ForegroundColor Yellow
Write-Host ("浏览器打开 : http://localhost:{0}/" -f $MirrorPort) -ForegroundColor White
Write-Host ("工具链 : 默认已注入(纯净页 http://localhost:{0}/#nohornet);切勿加查询参数 ?xxx —— 会被游戏当成 config 参数,导致握手失败 1005" -f $MirrorPort) -ForegroundColor White
if ($WithRelay) {
Write-Host ("连接路径 : 页面 → ws://localhost:{0}/ (中继)→ {1}" -f $RelayPort, $Upstream) -ForegroundColor White
$DiskInfo = if ($DumpEvery -gt 0 -or -not $NoLog) { "dump-every=$DumpEvery log=$(if ($NoLog) {'off'} else {'on'}) hex=$(if ($NoHex) {'off'} else {'on'})" } else { '不落盘(只转发 + 实时解析)' }
Write-Host ("抓包目录 : {0} (仅落盘时使用)" -f $PacketDir) -ForegroundColor White
Write-Host ("落盘策略 : {0} (默认落盘保存;不落盘加 -DumpEvery 0 -NoLog)" -f $DiskInfo) -ForegroundColor White
Write-Host ("进程 : 中继 PID={0} 镜像 PID={1}" -f $RelayProc.Id, $MirrorProc.Id) -ForegroundColor White
Write-Host ("自检 : 中继={0} 镜像={1}" -f ($(if ($RelayOk) {'OK'} else {'未监听'})), ($(if ($MirrorOk) {'OK'} else {'未监听'}))) -ForegroundColor White
} else {
Write-Host '连接路径 : 页面 → config 取自真实站点 → 直连 wss://c1.3dtank.com:9090/(未启动中继)' -ForegroundColor White
Write-Host ("进程 : 镜像 PID={0}" -f $MirrorProc.Id) -ForegroundColor White
Write-Host ("自检 : 镜像={0}" -f ($(if ($MirrorOk) {'OK'} else {'未监听'}))) -ForegroundColor White
}
Write-Host '停止 : 关掉 node 窗口,或 Stop-Process -Id <PID>' -ForegroundColor DarkGray
Write-Host '=========================================' -ForegroundColor Yellow
if ($WithRelay -and -not $RelayOk) { Write-Warning '中继未监听,查看它的 node 窗口报错(常见:ws 未装上/端口被占)' }
if (-not $MirrorOk) { Write-Warning '本地镜像未监听,查看它的 node 窗口报错' }
if ($OpenBrowser -and $MirrorOk) { Start-Process ("http://localhost:{0}/" -f $MirrorPort) }