Files
TankIC/3DTank/Start-LocalAndRelay.ps1
T
TermiNexus cca13974de fix(relay): 上游对齐官方 —— config 回源与中继上游改为 c2
- serve.js:新增 --config-upstream(默认 https://c2.3dtank.com),/config.xml 回源与改写底稿都改用它

- wss_server.js / Start-LocalAndRelay.ps1:上游默认 c1 改为 c2,可用 -Upstream / --upstream 切回

- 依据:客户端 JS 无 c1/c2 硬编码(连谁完全由 config 决定);实测 c1/c2 各自提供指向自己的 config,官方客户端实际连 c2
2026-10-15 14:09:25 +08:00

189 lines
10 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:-WithRelay 时由 serve.js 动态改写为 ws://localhost:<RelayPort>/(不落盘);否则代理真实 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://c2.3dtank.com:9090/', # 官方客户端实际连 c2(c1/c2 是两个独立服;要切回 c1 用 -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)时改用 --relay-port:serve.js 动态把 config 改写为 ws://localhost:<RelayPort>/(不落盘)
$MirrorArgs = @('serve.js', '--port', "$MirrorPort")
if ($WithRelay) { $MirrorArgs += @('--relay-port', "$RelayPort") } else { $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) }