# Identify every top-level window the pod owns (real titles + class names), # then X-close ONLY the main game window -- the actual user action. param([int]$CloseAfterLaunch = 5, [string]$Tag = 'mainwin', [switch]$SkipLaunch) Add-Type @' using System; using System.Text; using System.Runtime.InteropServices; public class W4 { [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern bool EnumWindows(EnumProc cb, IntPtr p); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr h, StringBuilder s, int n); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassNameW(IntPtr h, StringBuilder s, int n); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h); [DllImport("user32.dll")] public static extern bool IsWindow(IntPtr h); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern bool PostMessageW(IntPtr h, uint m, IntPtr w, IntPtr l); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; } public delegate bool EnumProc(IntPtr h, IntPtr p); public class Win { public IntPtr H; public string Title; public string Cls; public bool Vis; public int W, Ht; } public static System.Collections.Generic.List List = new System.Collections.Generic.List(); public static uint Target; public static bool Cb(IntPtr h, IntPtr p) { uint pid; GetWindowThreadProcessId(h, out pid); if (pid == Target) { StringBuilder t = new StringBuilder(256); GetWindowTextW(h, t, 256); StringBuilder c = new StringBuilder(256); GetClassNameW(h, c, 256); RECT r; GetWindowRect(h, out r); List.Add(new Win { H=h, Title=t.ToString(), Cls=c.ToString(), Vis=IsWindowVisible(h), W=r.R-r.L, Ht=r.B-r.T }); } return true; } public static void Scan(uint pid) { List.Clear(); Target = pid; EnumWindows(Cb, IntPtr.Zero); } } '@ $content = 'C:\git\bt411\content' $mine = @() function Pods { @(Get-CimInstance Win32_Process -Filter "Name='btl4.exe'" | Select-Object -ExpandProperty ProcessId) } function ShowWins($pid_, $label) { [W4]::Scan([uint32]$pid_) " $label" foreach ($w in [W4]::List) { " hwnd={0,-10} vis={1,-5} {2,4}x{3,-4} class='{4}' title='{5}'" -f $w.H.ToInt64(), $w.Vis, $w.W, $w.Ht, $w.Cls, $w.Title } } $relayLog = Join-Path $content "orphan_relay_$Tag.log" $relay = Start-Process 'python' -ArgumentList 'C:\git\bt411\tools\btconsole.py','--relay','1600','OPERATOR.EGG' ` -WorkingDirectory $content -PassThru -RedirectStandardOutput $relayLog ` -RedirectStandardError (Join-Path $content "orphan_relay_$Tag.err") -WindowStyle Hidden $mine += $relay.Id Start-Sleep -Seconds 4 $log = "orphan_$Tag.log" Remove-Item (Join-Path $content $log) -Force -ErrorAction SilentlyContinue $env:BT_LOG=$log; $env:BT_LOG_APPEND='1'; $env:BT_START_INSIDE='1'; $env:BT_DEV_GAUGES='1' $env:BT_PLATFORM='glass'; $env:BT_RELAY='127.0.0.1:1600'; $env:BT_FE_LOOP='1'; $env:BT_CALLSIGN='ORPHANTEST' $pod = Start-Process 'C:\git\bt411\build\Release\btl4.exe' ` -ArgumentList '-egg','OPERATOR.EGG','-net','1601','-platform','glass' ` -WorkingDirectory $content -PassThru $mine += $pod.Id "pod pid $($pod.Id)" Start-Sleep -Seconds 20 ShowWins $pod.Id "WINDOWS IN THE JOIN WAIT:" if (-not $SkipLaunch) { $secret = (Get-Content (Join-Path $content 'operator_secret.txt') -Raw).Trim() $cli = New-Object System.Net.Sockets.TcpClient('127.0.0.1', 1607) $wr = New-Object System.IO.StreamWriter($cli.GetStream()); $wr.AutoFlush = $true $wr.WriteLine("AUTH $secret"); Start-Sleep -Milliseconds 700; $wr.WriteLine("launch") "LAUNCH sent; closing in ${CloseAfterLaunch}s" Start-Sleep -Seconds $CloseAfterLaunch ShowWins $pod.Id "WINDOWS MID-LOAD:" } # The MAIN game window: the big visible one (the D3D target), not a panel. [W4]::Scan([uint32]$pod.Id) $main = [W4]::List | Where-Object { $_.Vis } | Sort-Object { $_.W * $_.Ht } -Descending | Select-Object -First 1 "--- X-closing ONLY the main window: hwnd=$($main.H.ToInt64()) '$($main.Title)' $($main.W)x$($main.Ht) ---" [void][W4]::PostMessageW($main.H, 0x0010, [IntPtr]::Zero, [IntPtr]::Zero) $exited = $false; $secs = 0 for ($i = 1; $i -le 45; $i++) { Start-Sleep -Milliseconds 1000 if (-not $exited -and (Get-Process -Id $pod.Id -ErrorAction SilentlyContinue) -eq $null) { $exited=$true; $secs=$i } } "" "RESULT exited : $exited$(if($exited){" after ${secs}s"})" if (-not $exited) { ShowWins $pod.Id "*** ORPHAN SURVIVES -- its windows now: ***" " cpu=$([math]::Round((Get-Process -Id $pod.Id).CPU,1))s" Get-NetTCPConnection -OwningProcess $pod.Id -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Established' } | ForEach-Object { " STILL CONNECTED: :$($_.LocalPort) -> $($_.RemoteAddress):$($_.RemotePort)" } " --- relay's view (is the ghost still seated?) ---" Get-Content $relayLog -Tail 4 | ForEach-Object { " $_" } } "--- pod log tail ---" Get-Content (Join-Path $content $log) -Tail 5 | ForEach-Object { " $_" } "--- cleanup (only my pids: $($mine -join ',')) ---" foreach ($m in $mine) { Stop-Process -Id $m -Force -ErrorAction SilentlyContinue } Start-Sleep -Milliseconds 800 "btl4 remaining: $((Pods) -join ',')"