196 lines
11 KiB
PowerShell
196 lines
11 KiB
PowerShell
param([string]$Action)
|
|
$ErrorActionPreference = 'Stop'
|
|
$interactiveMenu = [string]::IsNullOrWhiteSpace($Action)
|
|
# User-requested delay after an interactive menu action finishes.
|
|
$menuCloseDelaySeconds = 5
|
|
$launcherExitCode = 0
|
|
Set-Location -LiteralPath $PSScriptRoot
|
|
. (Join-Path $PSScriptRoot 'tools/go-toolchain.ps1')
|
|
|
|
function Merge-Settings($Base, $Override) {
|
|
foreach ($property in $Override.PSObject.Properties) {
|
|
$existing = $Base.PSObject.Properties[$property.Name]
|
|
if ($existing -and $property.Value -is [pscustomobject] -and $existing.Value -is [pscustomobject]) {
|
|
Merge-Settings $existing.Value $property.Value
|
|
} else {
|
|
$Base | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value -Force
|
|
}
|
|
}
|
|
}
|
|
function Resolve-ProjectPath([string]$Path) {
|
|
if ([IO.Path]::IsPathRooted($Path)) { return [IO.Path]::GetFullPath($Path) }
|
|
return [IO.Path]::GetFullPath((Join-Path $PSScriptRoot $Path))
|
|
}
|
|
function Stop-ProjectServerAt([string]$Name, [string]$Target) {
|
|
Get-Process -Name $Name -ErrorAction SilentlyContinue | ForEach-Object {
|
|
if ($_.Path -and [string]::Equals($_.Path, $Target, [StringComparison]::OrdinalIgnoreCase)) {
|
|
Stop-Process -Id $_.Id -ErrorAction Stop
|
|
if (-not $_.WaitForExit(5000)) { throw "Server did not stop: $Target" }
|
|
}
|
|
}
|
|
}
|
|
function Stop-ProjectServer([string]$Name) {
|
|
Stop-ProjectServerAt $Name (Resolve-ProjectPath ($Name + '.exe'))
|
|
}
|
|
function Start-ProjectServer([string]$Name) {
|
|
$path = Resolve-ProjectPath ($Name + '.exe')
|
|
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "$Name is not built. Run build first." }
|
|
Stop-ProjectServer $Name
|
|
$process = Start-Process -FilePath $path -WorkingDirectory $PSScriptRoot -WindowStyle Normal -PassThru
|
|
if ($process.WaitForExit(250)) { throw "$Name exited with code $($process.ExitCode). Run .\$Name.exe in a terminal to inspect the startup error." }
|
|
Write-Host "Started $Name (PID $($process.Id)) in its own visible console."
|
|
}
|
|
function Build-Servers {
|
|
$go = Resolve-GoToolchain $settings.launcher
|
|
$shipDirectory = Resolve-ProjectPath 'for ship'
|
|
New-Item -ItemType Directory -Path $shipDirectory -Force | Out-Null
|
|
foreach ($name in @('gameserver', 'sdkserver')) {
|
|
$next = Resolve-ProjectPath ($name + '-go.next.exe')
|
|
& $go build -o $next ('./cmd/' + $name)
|
|
if ($LASTEXITCODE -ne 0) { throw "Build failed: $name. Existing binaries were not replaced." }
|
|
|
|
$shipName = $name + '-go'
|
|
$shipNext = Join-Path $shipDirectory ($shipName + '.next.exe')
|
|
& $go build -tags ship -o $shipNext ('./cmd/' + $name)
|
|
if ($LASTEXITCODE -ne 0) { throw "Shipping build failed: $name. Existing binaries were not replaced." }
|
|
}
|
|
foreach ($name in @('gameserver-go', 'sdkserver-go')) {
|
|
$next = Resolve-ProjectPath ($name + '.next.exe')
|
|
$target = Resolve-ProjectPath ($name + '.exe')
|
|
$shipNext = Join-Path $shipDirectory ($name + '.next.exe')
|
|
$shipTarget = Join-Path $shipDirectory ($name + '.exe')
|
|
Stop-ProjectServer $name
|
|
Stop-ProjectServerAt $name $shipTarget
|
|
Move-Item -LiteralPath $next -Destination $target -Force
|
|
Move-Item -LiteralPath $shipNext -Destination $shipTarget -Force
|
|
}
|
|
Write-Host "Build successful. Standard binaries are in the project root; self-contained binaries are in '$shipDirectory'."
|
|
}
|
|
function Start-External([string]$Setting, [string]$EnvironmentName) {
|
|
$path = [Environment]::GetEnvironmentVariable($EnvironmentName)
|
|
if ([string]::IsNullOrWhiteSpace($path)) { $path = $settings.launcher.$Setting }
|
|
if ([string]::IsNullOrWhiteSpace($path)) {
|
|
Write-Host "Servers started. Set launcher.$Setting or $EnvironmentName to launch the external program."
|
|
return
|
|
}
|
|
$path = Resolve-ProjectPath $path
|
|
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Configured program does not exist: $path" }
|
|
$directory = Split-Path -Parent $path
|
|
if ([IO.Path]::GetExtension($path) -in @('.bat', '.cmd')) {
|
|
Start-Process -FilePath 'cmd.exe' -ArgumentList ('/d /c ""' + $path + '""') -WorkingDirectory $directory -WindowStyle Hidden
|
|
} else {
|
|
Start-Process -FilePath $path -WorkingDirectory $directory
|
|
}
|
|
}
|
|
try {
|
|
$configPath = $env:GAC_CONFIG
|
|
if ([string]::IsNullOrWhiteSpace($configPath)) { $configPath = 'server.json' }
|
|
$configPath = Resolve-ProjectPath $configPath
|
|
$env:GAC_CONFIG = $configPath
|
|
$settings = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
|
|
$localPath = [IO.Path]::ChangeExtension($configPath, 'local.json')
|
|
if (Test-Path -LiteralPath $localPath) {
|
|
Merge-Settings $settings (Get-Content -LiteralPath $localPath -Raw | ConvertFrom-Json)
|
|
}
|
|
$variables = @{
|
|
account_name='GAC_ACCOUNT_NAME';
|
|
player_uid='GAC_PLAYER_UID'; client_version='GAC_CLIENT_VERSION'; game_host='GAC_GAME_HOST'; game_port='GAC_GAME_PORT';
|
|
sdk_host='GAC_SDK_HOST'; sdk_port='GAC_SDK_PORT'; public_game_host='GAC_PUBLIC_GAME_HOST';
|
|
public_sdk_url='GAC_PUBLIC_SDK_URL'
|
|
}
|
|
foreach ($key in $variables.Keys) {
|
|
$value = [Environment]::GetEnvironmentVariable($variables[$key])
|
|
if ($null -ne $value) { $settings | Add-Member -MemberType NoteProperty -Name $key -Value $value -Force }
|
|
}
|
|
foreach ($pair in @(@('begin_time','GAC_ACTIVITY_BEGIN_TIME'),@('end_time','GAC_ACTIVITY_END_TIME'))) {
|
|
$value = [Environment]::GetEnvironmentVariable($pair[1])
|
|
if ($null -ne $value) {
|
|
if (-not $settings.activity_schedule) { $settings | Add-Member -MemberType NoteProperty -Name activity_schedule -Value ([pscustomobject]@{}) }
|
|
$settings.activity_schedule | Add-Member -MemberType NoteProperty -Name $pair[0] -Value $value -Force
|
|
}
|
|
}
|
|
$liveStaleSecondsText = [string]$settings.battle_diagnostics.live_report_stale_seconds
|
|
$liveStaleSecondsOverride = [Environment]::GetEnvironmentVariable('GAC_LIVE_BATTLE_STALE_SECONDS')
|
|
if ($null -ne $liveStaleSecondsOverride) {
|
|
if (-not $settings.battle_diagnostics) {
|
|
$settings | Add-Member -MemberType NoteProperty -Name battle_diagnostics -Value ([pscustomobject]@{})
|
|
}
|
|
$settings.battle_diagnostics | Add-Member -MemberType NoteProperty -Name live_report_stale_seconds -Value $liveStaleSecondsOverride -Force
|
|
$liveStaleSecondsText = $liveStaleSecondsOverride
|
|
}
|
|
$liveStaleSeconds = [uint16]0
|
|
if (-not [uint16]::TryParse($liveStaleSecondsText, [ref]$liveStaleSeconds) -or $liveStaleSeconds -eq 0) {
|
|
throw 'battle_diagnostics.live_report_stale_seconds must be an integer between 1 and 65535.'
|
|
}
|
|
$activityBegin = [long]0
|
|
$activityEnd = [long]0
|
|
if (-not $settings.activity_schedule -or
|
|
$null -eq $settings.activity_schedule.begin_time -or $null -eq $settings.activity_schedule.end_time -or
|
|
-not [long]::TryParse([string]$settings.activity_schedule.begin_time,[ref]$activityBegin) -or
|
|
-not [long]::TryParse([string]$settings.activity_schedule.end_time,[ref]$activityEnd) -or
|
|
$activityBegin -lt 0 -or $activityEnd -le $activityBegin) {
|
|
throw 'activity_schedule requires nonnegative begin_time and end_time greater than begin_time.'
|
|
}
|
|
$configuredUID = [uint32]0
|
|
if (-not [uint32]::TryParse([string]$settings.player_uid, [ref]$configuredUID) -or $configuredUID -eq 0) {
|
|
throw 'player_uid must be an integer between 1 and 4294967295.'
|
|
}
|
|
foreach ($key in @('game_port','sdk_port')) {
|
|
$port = 0
|
|
if (-not [int]::TryParse([string]$settings.$key, [ref]$port) -or $port -lt 1 -or $port -gt [uint16]::MaxValue) {
|
|
throw "$key must be a port between 1 and $([uint16]::MaxValue)."
|
|
}
|
|
}
|
|
foreach ($key in @('account_name','client_version','game_host','sdk_host','public_game_host','public_sdk_url')) {
|
|
if ($settings.$key -isnot [string] -or [string]::IsNullOrWhiteSpace($settings.$key)) { throw "Missing or invalid string setting: $key" }
|
|
}
|
|
foreach ($key in @('game_host','sdk_host','public_game_host')) {
|
|
if ([string]$settings.$key -match '[/\\\s]') { throw "Invalid host: $key" }
|
|
}
|
|
$uri = $null
|
|
if (-not [Uri]::TryCreate($settings.public_sdk_url, [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -notin @('http','https') -or $uri.Query -or $uri.Fragment -or $uri.UserInfo) {
|
|
throw 'public_sdk_url must be an HTTP(S) base URL.'
|
|
}
|
|
$leaseSeconds = [uint32]0
|
|
if ($settings.srtool.lease_file -isnot [string] -or [string]::IsNullOrWhiteSpace($settings.srtool.lease_file) -or -not [uint32]::TryParse([string]$settings.srtool.lease_seconds,[ref]$leaseSeconds) -or $leaseSeconds -eq 0) { throw 'Set srtool.lease_file and positive uint32 srtool.lease_seconds.' }
|
|
$install = $settings.launcher.go_install
|
|
if ($install -and $install.enabled) {
|
|
if ([string]::IsNullOrWhiteSpace($install.directory)) { throw 'Set launcher.go_install.directory.' }
|
|
foreach ($key in @('manifest_url','download_url')) {
|
|
$uri = $null
|
|
if (-not [Uri]::TryCreate($install.$key, [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -ne 'https' -or $uri.UserInfo -or $uri.Fragment) { throw "launcher.go_install.$key must be an HTTPS URL." }
|
|
}
|
|
}
|
|
if ($Action -eq 'check') { Write-Host "Configuration valid. Activity schedule: $activityBegin -> $activityEnd"; exit 0 }
|
|
if ([string]::IsNullOrWhiteSpace($Action)) {
|
|
Write-Host "GaC hkrpg GO | Client $($settings.client_version)"
|
|
Write-Host "Game UDP $($settings.game_host):$($settings.game_port) | SDK HTTP $($settings.sdk_host):$($settings.sdk_port)"
|
|
Write-Host '[1] Build, start servers and game (default)'
|
|
Write-Host '[2] Start servers | [3] SDK only | [4] Game server only'
|
|
Write-Host '[5] Start servers and proxy | [6] Build only | [7] Exit | [8] Stop servers'
|
|
Write-Host '[9] Check/install Go build requirement'
|
|
$Action = Read-Host 'Select'
|
|
if ([string]::IsNullOrWhiteSpace($Action)) { $Action = '1' }
|
|
}
|
|
switch ($Action.Trim().ToLowerInvariant()) {
|
|
{ $_ -in @('1','game') } { Build-Servers; Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; Start-External 'client_path' 'GAC_CLIENT_PATH'; break }
|
|
{ $_ -in @('2','servers') } { Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; break }
|
|
{ $_ -in @('3','sdk') } { Start-ProjectServer 'sdkserver-go'; break }
|
|
{ $_ -in @('4','gs') } { Start-ProjectServer 'gameserver-go'; break }
|
|
{ $_ -in @('5','proxy') } { Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; Start-External 'proxy_path' 'GAC_PROXY_PATH'; break }
|
|
{ $_ -in @('6','build') } { Build-Servers; break }
|
|
{ $_ -in @('9','requirements') } { $go = Resolve-GoToolchain $settings.launcher; Write-Host "Go ready: $go"; break }
|
|
'7' { break }
|
|
{ $_ -in @('8','stop') } { Stop-ProjectServer 'sdkserver-go'; Stop-ProjectServer 'gameserver-go'; break }
|
|
default { throw "Unknown action: $Action" }
|
|
}
|
|
} catch {
|
|
Write-Host "[ERROR] $($_.Exception.Message)" -ForegroundColor Red
|
|
$launcherExitCode = 1
|
|
}
|
|
if ($interactiveMenu) {
|
|
Write-Host "Launcher closes in $menuCloseDelaySeconds seconds. Server windows stay open."
|
|
Start-Sleep -Seconds $menuCloseDelaySeconds
|
|
}
|
|
exit $launcherExitCode
|