- 实测: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 引入
189 lines
9.8 KiB
PowerShell
189 lines
9.8 KiB
PowerShell
<#
|
||
.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) }
|