Files
TankIC/3DTank/Start-LocalAndRelay.ps1
T

225 lines
12 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
.EXAMPLE
// 强制整个链路(前端 config 来源 + 中继上游)都走 c1
// 注意:PowerShell 不认 -SRV=1 这种等号写法,要写 -SRV 1 或 -SRV:1
.\Start-LocalAndRelay.ps1 -SRV 1 -WithRelay -OpenBrowser
.EXAMPLE
// 强制走 c2(官方默认主服)
.\Start-LocalAndRelay.ps1 -SRV 2 -OpenBrowser
#>
# 注意:这里刻意不用 [CmdletBinding()]。
# PowerShell 原生不支持 -SRV=1 这种等号写法(会报 "parameter cannot be found"),
# 去掉 CmdletBinding 后,未绑定的 -SRV=1 会落进 $args,由下面手动解析兼容。
param(
[int]$MirrorPort = 8080,
[int]$RelayPort = 9091,
[string]$Upstream = 'wss://c2.3dtank.com:9090/', # 主上游(大厅:首帧 = Hash Request)
[string]$UpstreamAlt = 'wss://c1.3dtank.com:9090/', # 备用上游(首帧直接 EnterSpace 的「二次进空间」连接)
[ValidateSet(0, 1, 2)][int]$SRV = 0, # 服务器选择:0=保持默认(c2);1=前后端都走 c1.3dtank.com;2=前后端都走 c2.3dtank.com
[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'
# ---------- 兼容 -SRV=1 / --SRV=1 等号写法 ----------
# PowerShell 只认 -SRV 1 或 -SRV:1;等号形式会被当作未绑定参数落进 $args,这里手动识别
foreach ($A in $args) {
if ($A -is [string] -and $A -match '^--?SRV=(.+)$') {
$SRV = [int]$Matches[1]
}
}
if ($SRV -notin 0, 1, 2) { throw "-SRV 只能是 0 / 1 / 2(收到: $SRV)" }
$Project = $PSScriptRoot # 3DTank/
$Tools = Join-Path $Project 'tools'
$PacketDir = Join-Path $Project ("packets\{0}" -f $Label)
# ---------- 服务器选择(-SRV 1/2)----------
# 同时决定:① 前端 /config.xml 的回源(真实 config 里的 server address)
# ② 中继 --upstream(后端实际连哪台)
$ConfigUpstream = 'https://c2.3dtank.com'
if ($SRV -eq 1 -or $SRV -eq 2) {
$ServerHost = "c$SRV.3dtank.com"
$Upstream = "wss://${ServerHost}:9090/"
$UpstreamAlt = $Upstream
$ConfigUpstream = "https://$ServerHost"
Write-Host ("[服务器] -SRV={0} → 前端 config 与中继上游统一走 {1}" -f $SRV, $ServerHost) -ForegroundColor Yellow
}
# ---------- 小工具 ----------
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, '--upstream-alt', $UpstreamAlt, '--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 取真实站点(--config-upstream 决定 c1 / c2),游戏据此直连对应服务器
# 走中继(-WithRelay)时改用 --relay-port:serve.js 动态把 config 改写为 ws://localhost:<RelayPort>/(不落盘)
$MirrorArgs = @('serve.js', '--port', "$MirrorPort", '--config-upstream', $ConfigUpstream)
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 取自 {0} → 直连 {1}(未启动中继)" -f $ConfigUpstream, $Upstream) -ForegroundColor White
Write-Host ("服务器 : {0}" -f ($(if ($SRV -eq 0) {'-SRV 未指定(默认 c2)'} else {"-SRV=$SRV"}))) -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) }