Files
esefandClaude Opus 5.5 4b7fdcaa23 Windows version: host the server from your own PC
setup.cmd installs the same DM/GunGame server natively on Windows 10/11
(no Docker or Python) into Documents\cs16, then helps set up how friends
join (LAN, playit.gg, port forwarding, VPN). cs.cmd is the menu.

Also: setup falls back to backup copies of the freely licensed mod
downloads on this Gitea when a main site is down (tools/make-mirror.sh
fills them), and the README is rewritten as a plain guide.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 20:55:00 +03:00

131 lines
7.1 KiB
PowerShell

# Puts the mod stack on top of the Valve game files in hlds\ (Windows twin of tools/install-addons.sh).
# setup.ps1 runs it after every game download/update: a Steam "validate" puts Valve's engine back.
# Safe to run again. Kept on re-runs: maps, stats, bot navigation, and your edits to users.ini
# (admins), amxx.cfg, csdm\config.ini, mapcycle.txt and custom.cfg.
param([Parameter(Mandatory = $true)][string]$Root)
. (Join-Path $PSScriptRoot 'common.ps1')
$DL = Join-Path $Root 'dl'; $H = Join-Path $Root 'hlds'; $C = Join-Path $H 'cstrike'
$A = Join-Path $C 'addons'; $CF = Join-Path $A 'amxmodx\configs'
if (-not (Test-Path -LiteralPath (Join-Path $H 'hlds.exe'))) { throw "no game files in $H (the Steam download didn't finish)" }
function Zip($pattern) {
$f = @(Get-ChildItem -LiteralPath $DL -Filter $pattern | Sort-Object Name | Select-Object -Last 1)
if ($f.Count -eq 0) { throw "missing download $pattern in $DL" }
return $f[0].FullName
}
function Read-ZipText($zip, $entry) {
$z = [IO.Compression.ZipFile]::OpenRead($zip)
try {
$e = $z.GetEntry($entry); if (-not $e) { throw "$entry not found in $zip" }
$r = New-Object IO.StreamReader $e.Open(); try { return $r.ReadToEnd() } finally { $r.Close() }
} finally { $z.Dispose() }
}
function Edit-File($path, [scriptblock]$change) { Write-TextFile $path (& $change ([IO.File]::ReadAllText($path))) }
Step 'downloads (pinned, checked against tools/versions*.txt)'
foreach ($item in Get-PinnedDownloads $Root) { [void](Get-Pinned $item $DL) }
# Game mode in use right now (re-run) or from settings.txt (first install)
# (the plugin lists only say something after a finished install: the repo itself ships an active
# plugins-gungame.ini, so an interrupted first install would otherwise come back as GunGame)
$settings = Read-Settings (Join-Path $Root 'settings.txt')
$first = -not (Test-Path -LiteralPath "$C\.cs16-dm-server")
if ($first) { $mode = $settings.CS_MODE }
elseif (Test-Path -LiteralPath "$CF\plugins-gungame.ini") { $mode = 'gg' }
else { $mode = 'dm' }
if ($mode -ne 'dm' -and $mode -ne 'gg') { throw "CS_MODE must be dm or gg (is: $mode)" }
# Files people edit: save them now, put them back after the archives are unpacked
$keepDir = Join-Path $DL '.keep'
if (Test-Path -LiteralPath $keepDir) { Remove-Item -LiteralPath $keepDir -Recurse -Force }
if (-not $first) {
foreach ($f in 'addons\amxmodx\configs\users.ini', 'addons\amxmodx\configs\amxx.cfg', 'addons\amxmodx\configs\csdm\config.ini') {
if (Test-Path -LiteralPath "$C\$f") {
New-Item -ItemType Directory -Force -Path (Split-Path "$keepDir\$f") | Out-Null
Copy-Item -LiteralPath "$C\$f" -Destination "$keepDir\$f" -Force
}
}
}
Step 'ReHLDS (engine)'
Expand-ZipPart (Zip 'rehlds-bin-*.zip') 'bin/win32/' $H
Step 'ReGameDLL (game logic)'
Expand-ZipPart (Zip 'regamedll-bin-*.zip') 'bin/win32/cstrike/' $C
# game.cfg runs at every map start and would force alltalk off: server.cfg decides that instead
Edit-File "$C\game.cfg" { param($t) $t -replace '(?m)^sv_alltalk "0"', '// sv_alltalk "0" // cs16-dm-server: set in server.cfg instead' }
Step 'Metamod-R'
Expand-ZipPart (Zip 'metamod-bin-*.zip') 'addons/' $A
if (-not (Test-Path -LiteralPath "$C\liblist.gam.orig")) { Copy-Item -LiteralPath "$C\liblist.gam" -Destination "$C\liblist.gam.orig" }
Edit-File "$C\liblist.gam" { param($t) $t -replace '(?m)^gamedll ".*"', 'gamedll "addons\metamod\metamod.dll"' }
Step 'AMX Mod X + ReAPI'
Expand-ZipPart (Zip 'amxmodx-*-base-windows.zip') '' $C
Expand-ZipPart (Zip 'amxmodx-*-cstrike-windows.zip') '' $C
Expand-ZipPart (Zip 'reapi-bin-*.zip') 'addons/' $A
Step 'ReUnion (non-Steam clients)'
$reu = Zip 'reunion-*.zip'
Expand-ZipPart $reu 'bin/Windows/' "$A\reunion"
$salt = ([IO.File]::ReadAllText((Join-Path $Root 'secrets\reunion-salt.txt'))).Trim()
if (-not $salt) { throw 'secrets\reunion-salt.txt is empty' }
# IDClientsLimit 0: several non-Steam players with the same placeholder ID can all join
$cfg = (Read-ZipText $reu 'reunion.cfg') -replace '(?m)^SteamIdHashSalt =.*$', "SteamIdHashSalt = $salt" -replace '(?m)^IDClientsLimit = .*$', 'IDClientsLimit = 0'
Write-TextFile "$C\reunion.cfg" $cfg
Step 'CSDM-ReAPI (deathmatch) + spawn points'
Expand-ZipPart (Zip 'CSDM-ReAPI-*.zip') 'cstrike/' $C
New-Item -ItemType Directory -Force -Path "$CF\csdm\spawns" | Out-Null
foreach ($f in Get-ChildItem -LiteralPath "$DL\spawns" -Filter *.spawns.cfg) {
$lines = [IO.File]::ReadAllLines($f.FullName) | ForEach-Object { Convert-SpawnLine $_ }
$text = ($lines -join "`n") + "`n"
Write-TextFile "$CF\csdm\spawns\$($f.Name)" $text
Write-TextFile "$CF\csdm\$($f.Name)" $text # GunGame looks for them one folder up (Linux: a symlink)
}
Step 'YaPB (bots)'
Expand-ZipPart (Zip 'yapb-*-windows.zip') '' $C
# FFA-aware bots; quota = fill up to CS_BOTS players (Start-CsServer sets the number)
Edit-File "$A\yapb\conf\yapb.cfg" { param($t) $t -replace '(?m)^yb_csdm_mode ".*"', 'yb_csdm_mode "2"' -replace '(?m)^yb_quota_mode ".*"', 'yb_quota_mode "fill"' }
Step 'GunGame + announcer sounds'
Copy-Item -LiteralPath "$DL\gg\gungame.sma" -Destination "$A\amxmodx\scripting\" -Force
Copy-Item -LiteralPath "$DL\gg\gungame.txt" -Destination "$A\amxmodx\data\lang\" -Force
New-Item -ItemType Directory -Force -Path "$C\sound\gungame", "$C\sound\misc" | Out-Null
Copy-Item -Path "$DL\gg\*.wav" -Destination "$C\sound\gungame\" -Force
Copy-Item -Path "$DL\snd\*.wav" -Destination "$C\sound\misc\" -Force
Step 'configs from the repo'
Copy-Item -Path (Join-Path $Root 'cstrike\*') -Destination $C -Recurse -Force
if ($first) { Copy-Item -LiteralPath (Join-Path $Root 'defaults\mapcycle.txt') -Destination "$C\mapcycle.txt" -Force }
if (-not (Test-Path -LiteralPath "$C\custom.cfg")) { Copy-Item -LiteralPath (Join-Path $Root 'defaults\custom.cfg') -Destination "$C\custom.cfg" }
if (Test-Path -LiteralPath $keepDir) {
Copy-Item -Path "$keepDir\*" -Destination $C -Recurse -Force
Remove-Item -LiteralPath $keepDir -Recurse -Force
}
Step 'compiling plugins'
Copy-Item -Path (Join-Path $Root 'plugins\*.sma') -Destination "$A\amxmodx\scripting\" -Force
Push-Location "$A\amxmodx\scripting"
try {
$ErrorActionPreference = 'Continue' # 5.1 turns a native program's stderr into a fatal error under 'Stop'
foreach ($p in 'rtv', 'esef_extras', 'esef_mode', 'gungame') {
$out = & .\amxxpc.exe "$p.sma" "-o..\plugins\$p.amxx" 2>&1
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath "..\plugins\$p.amxx")) { $out | Write-Host; throw "compiling $p.sma failed" }
}
} finally { Pop-Location; $ErrorActionPreference = 'Stop' }
Step "game mode: $mode"
# exactly one of the two plugin lists is active (.ini), the other one is .ini.off
foreach ($ini in 'plugins-gungame.ini', 'plugins-CSDM_ReAPI.ini') {
if (Test-Path -LiteralPath "$CF\$ini") { Remove-Item -LiteralPath "$CF\$ini.off" -Force -ErrorAction SilentlyContinue } # fresh copy wins
else { Move-Item -LiteralPath "$CF\$ini.off" -Destination "$CF\$ini" }
}
if ($mode -eq 'gg') { Move-Item -LiteralPath "$CF\plugins-CSDM_ReAPI.ini" -Destination "$CF\plugins-CSDM_ReAPI.ini.off" }
else { Move-Item -LiteralPath "$CF\plugins-gungame.ini" -Destination "$CF\plugins-gungame.ini.off" }
Write-TextFile "$C\.cs16-dm-server" ''
Write-Host ' mod stack installed'