# PokeOne Launcher: okienko update -> start klienta (ekran logowania) # Start: Start_PokeOne.vbs albo URUCHOM_POKEONE.bat param( [switch]$Silent, [switch]$Force, [switch]$NoStart, # Called from pokeone_DX9.exe (client_updater module). If already current: exit quietly. # If outdated: update (kills running client) and restart game. [switch]$FromClient ) $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $ClientDir = Split-Path -Parent $MyInvocation.MyCommand.Path if (-not $ClientDir) { $ClientDir = (Get-Location).Path } Set-Location $ClientDir $VersionUrl = 'https://poke-one.pl/downloads/version.json' $LocalVersionFile = Join-Path $ClientDir 'client_version.json' $LogFile = Join-Path $ClientDir 'update.log' $ExeDx9 = Join-Path $ClientDir 'pokeone_DX9.exe' $ExeOgl = Join-Path $ClientDir 'pokeone_OpenGL.exe' $script:StoppedGameForUpdate = $false # FromClient implies silent UI unless we actually need to download if ($FromClient) { $Silent = $true } function Write-Log([string]$msg) { $line = '[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg Add-Content -LiteralPath $LogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue } function Get-Sha256([string]$path) { $sha = [Security.Cryptography.SHA256]::Create() $stream = [IO.File]::OpenRead($path) try { return ([BitConverter]::ToString($sha.ComputeHash($stream))).Replace('-', '').ToLowerInvariant() } finally { $stream.Dispose() $sha.Dispose() } } function Expand-Zip([string]$zipPath, [string]$destination) { Add-Type -AssemblyName System.IO.Compression.FileSystem $root = [IO.Path]::GetFullPath($destination) [IO.Directory]::CreateDirectory($root) | Out-Null $prefix = $root.TrimEnd('\') + '\' $archive = [IO.Compression.ZipFile]::OpenRead($zipPath) try { $total = [long]0 foreach ($entry in $archive.Entries) { $total += [long]$entry.Length } $done = [long]0 $lastPercent = -1 $lastStatus = (Get-Date).AddSeconds(-5) $buffer = New-Object byte[] 131072 foreach ($entry in $archive.Entries) { $relative = $entry.FullName.Replace('/', '\') if (-not $relative) { continue } $path = [IO.Path]::GetFullPath([IO.Path]::Combine($root, $relative)) if (-not $path.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { throw ('Niebezpieczna sciezka w ZIP: ' + $entry.FullName) } if ($relative.EndsWith('\') -or -not $entry.Name) { [IO.Directory]::CreateDirectory($path) | Out-Null continue } [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($path)) | Out-Null $source = $null $target = $null try { $source = $entry.Open() $target = [IO.File]::Open($path, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::None) while (($count = $source.Read($buffer, 0, $buffer.Length)) -gt 0) { $target.Write($buffer, 0, $count) $done += $count [System.Windows.Forms.Application]::DoEvents() $percent = if ($total -gt 0) { [int][Math]::Min(95, 92 + [Math]::Floor(3 * $done / $total)) } else { 95 } if ($percent -gt $lastPercent -or (Get-Date) -ge $lastStatus.AddSeconds(3)) { Set-Status ('Rozpakowuje: ' + [Math]::Round($done / 1MB, 0) + ' / ' + [Math]::Round($total / 1MB, 0) + ' MB') $percent $lastPercent = $percent $lastStatus = Get-Date } } } finally { if ($target) { $target.Dispose() } if ($source) { $source.Dispose() } } } } finally { $archive.Dispose() } } function Get-LocalVersion { if (-not (Test-Path -LiteralPath $LocalVersionFile)) { return '0' } try { $j = Get-Content -LiteralPath $LocalVersionFile -Raw -Encoding UTF8 | ConvertFrom-Json if ($j.version) { return [string]$j.version } } catch { return '0' } return '0' } function Save-LocalVersion([string]$ver, [string]$sha) { $obj = [ordered]@{ version = $ver sha256 = $sha updatedAt = (Get-Date).ToString('o') } $json = ($obj | ConvertTo-Json -Compress) $utf8 = New-Object System.Text.UTF8Encoding $false [IO.File]::WriteAllText($LocalVersionFile, $json, $utf8) } $script:Ui = $null function Set-Status([string]$text, [int]$pct = -1) { Write-Log $text if ($Silent) { Write-Host $text return } if ($script:Ui -and $script:Ui.Label) { $script:Ui.Label.Text = $text if ($pct -ge 0 -and $script:Ui.Bar) { $script:Ui.Bar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous $script:Ui.Bar.Value = [Math]::Min(100, [Math]::Max(0, $pct)) } [System.Windows.Forms.Application]::DoEvents() } } function New-LauncherUi { $form = New-Object System.Windows.Forms.Form $form.Text = 'PokeOne' $form.Size = New-Object System.Drawing.Size(420, 160) $form.StartPosition = 'CenterScreen' $form.FormBorderStyle = 'FixedDialog' $form.MaximizeBox = $false $form.MinimizeBox = $false $form.TopMost = $true $form.BackColor = [System.Drawing.Color]::FromArgb(15, 23, 42) $title = New-Object System.Windows.Forms.Label $title.Text = 'PokeOne' $title.ForeColor = [System.Drawing.Color]::FromArgb(125, 211, 252) $title.Font = New-Object System.Drawing.Font('Segoe UI', 14, [System.Drawing.FontStyle]::Bold) $title.AutoSize = $true $title.Location = New-Object System.Drawing.Point(18, 14) $form.Controls.Add($title) $label = New-Object System.Windows.Forms.Label $label.Text = 'Uruchamianie...' $label.ForeColor = [System.Drawing.Color]::FromArgb(226, 232, 240) $label.Font = New-Object System.Drawing.Font('Segoe UI', 9) $label.Size = New-Object System.Drawing.Size(370, 40) $label.Location = New-Object System.Drawing.Point(20, 48) $form.Controls.Add($label) $bar = New-Object System.Windows.Forms.ProgressBar $bar.Location = New-Object System.Drawing.Point(20, 95) $bar.Size = New-Object System.Drawing.Size(360, 18) $bar.Style = 'Marquee' $bar.MarqueeAnimationSpeed = 30 $form.Controls.Add($bar) $form.Show() [System.Windows.Forms.Application]::DoEvents() return @{ Form = $form; Label = $label; Bar = $bar } } function Start-Game { if ($NoStart) { return } $exe = $null if (Test-Path -LiteralPath $ExeDx9) { $exe = $ExeDx9 } elseif (Test-Path -LiteralPath $ExeOgl) { $exe = $ExeOgl } if (-not $exe) { Set-Status 'Brak pokeone_DX9.exe w folderze klienta.' if (-not $Silent) { [System.Windows.Forms.MessageBox]::Show('Brak pokeone_DX9.exe', 'PokeOne') } return } Set-Status 'Startuje gre (ekran logowania)...' 100 Start-Process -FilePath $exe -WorkingDirectory $ClientDir } function Read-RemoteJson([string]$url) { $request = [Net.HttpWebRequest][Net.WebRequest]::Create($url) $request.UserAgent = 'PokeOne-Launcher/1.0' $request.Timeout = 15000 $request.ReadWriteTimeout = 15000 $response = $null $reader = $null try { $task = $request.GetResponseAsync() $deadline = (Get-Date).AddSeconds(15) while (-not $task.IsCompleted) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 if ((Get-Date) -ge $deadline) { throw 'Serwer wersji nie odpowiada (15 s).' } } $response = [Net.HttpWebResponse]$task.GetAwaiter().GetResult() $reader = New-Object IO.StreamReader($response.GetResponseStream()) $read = $reader.ReadToEndAsync() $deadline = (Get-Date).AddSeconds(15) while (-not $read.IsCompleted) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 if ((Get-Date) -ge $deadline) { throw 'Odczyt wersji nie odpowiada (15 s).' } } $raw = [string]$read.GetAwaiter().GetResult() } finally { if ($reader) { $reader.Dispose() } if ($response) { $response.Dispose() } $request.Abort() } $raw = $raw -replace '^\uFEFF', '' $idx = $raw.IndexOf('{') if ($idx -lt 0) { throw 'version.json nie jest JSON' } if ($idx -gt 0) { $raw = $raw.Substring($idx) } return ($raw | ConvertFrom-Json) } function Stop-LocalGame { $root = [IO.Path]::GetFullPath($ClientDir).TrimEnd('\') + '\' $stopped = 0 foreach ($process in @(Get-Process pokeone_DX9, pokeone_OpenGL, otclient, pokedash_DX9 -ErrorAction SilentlyContinue)) { $path = $null try { $path = $process.Path } catch {} if (-not $path) { continue } if (-not ([IO.Path]::GetFullPath($path).StartsWith($root, [StringComparison]::OrdinalIgnoreCase))) { continue } Stop-Process -Id $process.Id -Force -ErrorAction Stop $script:StoppedGameForUpdate = $true $stopped++ } if ($stopped -gt 0) { Start-Sleep -Milliseconds 800 } return $stopped } function Assert-InstallWritable { $probe = Join-Path $ClientDir ('.pokeone_write_' + [guid]::NewGuid().ToString('N')) try { [IO.File]::WriteAllText($probe, 'ok') } catch { throw 'Brak uprawnien do zapisu w folderze klienta. Przenies klienta poza Program Files/OneDrive.' } finally { if (Test-Path -LiteralPath $probe) { Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue } } } function Download-FileResponsive([string]$url, [string]$destination, [long]$expectedSize) { $request = [Net.HttpWebRequest][Net.WebRequest]::Create($url) $request.UserAgent = 'PokeOne-Launcher/1.0' $request.Timeout = 30000 $request.ReadWriteTimeout = 45000 $response = $null $source = $null $target = $null try { $connection = $request.GetResponseAsync() $connectDeadline = (Get-Date).AddSeconds(35) while (-not $connection.IsCompleted) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 if ((Get-Date) -ge $connectDeadline) { throw 'Serwer pobierania nie odpowiada (35 s).' } } $response = [Net.HttpWebResponse]$connection.GetAwaiter().GetResult() if ($expectedSize -le 0) { $expectedSize = [long]$response.ContentLength } $source = $response.GetResponseStream() $target = [IO.File]::Open($destination, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::None) $buffer = New-Object byte[] 65536 $downloaded = [long]0 $lastPercent = -1 $lastStatus = (Get-Date).AddSeconds(-5) while ($true) { $read = $source.ReadAsync($buffer, 0, $buffer.Length) $readDeadline = (Get-Date).AddSeconds(45) while (-not $read.IsCompleted) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 if ((Get-Date) -ge $readDeadline) { throw 'Pobieranie zatrzymalo sie na ponad 45 sekund.' } } $count = [int]$read.GetAwaiter().GetResult() if ($count -le 0) { break } $target.Write($buffer, 0, $count) $downloaded += $count $percent = if ($expectedSize -gt 0) { [int][Math]::Min(85, 5 + [Math]::Floor(80 * $downloaded / $expectedSize)) } else { 5 } if ($percent -gt $lastPercent -or (Get-Date) -ge $lastStatus.AddSeconds(3)) { $mb = [Math]::Round($downloaded / 1MB, 1) $totalText = if ($expectedSize -gt 0) { ' / ' + [Math]::Round($expectedSize / 1MB, 1) + ' MB' } else { ' MB' } Set-Status ('Pobieranie: ' + $mb + $totalText) $percent $lastPercent = $percent $lastStatus = Get-Date } } $target.Flush() if ($expectedSize -gt 0 -and $downloaded -ne $expectedSize) { throw ('Pobrano ' + $downloaded + ' z ' + $expectedSize + ' bajtow.') } } finally { if ($target) { $target.Dispose() } if ($source) { $source.Dispose() } if ($response) { $response.Dispose() } $request.Abort() } } function Invoke-UpdateIfNeeded { Set-Status 'Sprawdzam aktualizacje...' $localVer = Get-LocalVersion $remote = $null try { $remote = Read-RemoteJson $VersionUrl } catch { Set-Status ('Brak netu / update - startuje lokalna wersja. (' + $_.Exception.Message + ')') return } $remoteVer = [string]$remote.version $zipUrl = [string]$remote.zipUrl if (-not $zipUrl) { $zipUrl = 'https://poke-one.pl/downloads/pokeone-client.zip' } $wantSha = ([string]$remote.sha256).ToLowerInvariant() $need = $Force -or (-not $localVer) -or ($localVer -eq '0') -or ($remoteVer -ne $localVer) if (-not $need) { Set-Status ('Klient aktualny (' + $localVer + ').') 100 return } $zipUri = $null if (-not [Uri]::TryCreate($zipUrl, [UriKind]::Absolute, [ref]$zipUri) -or $zipUri.Scheme -ne 'https' -or $zipUri.Host -ne 'poke-one.pl') { throw 'Nieprawidlowy adres paczki w version.json.' } if ($remoteVer -eq '' -or $wantSha -notmatch '^[a-f0-9]{64}$' -or [long]$remote.size -le 0) { throw 'Niepelny manifest aktualizacji. Nie instaluje paczki.' } Assert-InstallWritable Set-Status ('Nowa wersja ' + $remoteVer + ' - pobieram...') 5 # Keep the ZIP and extracted files on the same drive as the game. The system # TEMP directory is often on C:, even when PokeOne is installed on D:. $tmpParent = [IO.Path]::GetFullPath((Split-Path -Parent $ClientDir)) $tmpRoot = Join-Path $tmpParent ('pokeone_upd_' + [guid]::NewGuid().ToString('N')) $unpackedSize = [long]$remote.unpackedSize if ($unpackedSize -le 0) { $unpackedSize = [long]$remote.size * 4 } $requiredSpace = [long]$remote.size + 2 * $unpackedSize + 100MB $drive = [IO.DriveInfo]::new([IO.Path]::GetPathRoot($tmpRoot)) if ($drive.AvailableFreeSpace -lt $requiredSpace) { throw ('Za malo miejsca na dysku ' + $drive.Name + '. Potrzeba ok. ' + [Math]::Ceiling($requiredSpace / 1GB) + ' GB wolnego miejsca.') } $zipPath = Join-Path $tmpRoot 'pokeone-client.zip' $extractDir = Join-Path $tmpRoot 'extract' New-Item -ItemType Directory -Path $tmpRoot -Force | Out-Null try { for ($attempt = 1; $attempt -le 3; $attempt++) { try { Set-Status ('Pobieranie update ' + $remoteVer + ' (proba ' + $attempt + '/3)...') 5 Download-FileResponsive $zipUrl $zipPath ([long]$remote.size) Set-Status 'Sprawdzam pobrany plik...' 88 $got = Get-Sha256 $zipPath if ($got -ne $wantSha) { throw 'SHA256 nie pasuje - ponawiam pobieranie.' } break } catch { Write-Log ('Proba pobierania ' + $attempt + '/3: ' + $_.Exception.Message) if ($attempt -ge 3) { throw } Set-Status ('Polaczenie przerwane. Ponawiam (' + ($attempt + 1) + '/3)...') 5 Start-Sleep -Seconds 2 } } $preserve = @('pokeonerc.lua', 'otclientrc.lua', 'update.log') $backup = @{} foreach ($name in $preserve) { $p = Join-Path $ClientDir $name if (Test-Path -LiteralPath $p) { $backup[$name] = [IO.File]::ReadAllBytes($p) } } Set-Status 'Pobieranie OK. Zamykam klienta...' 90 Stop-LocalGame | Out-Null Set-Status 'Rozpakowuje aktualizacje...' 92 New-Item -ItemType Directory -Path $extractDir -Force | Out-Null Expand-Zip $zipPath $extractDir $roots = @(Get-ChildItem -LiteralPath $extractDir -Force) $src = $extractDir if ($roots.Count -eq 1 -and $roots[0].PSIsContainer) { $src = $roots[0].FullName } Set-Status 'Instaluje pliki...' 96 $copyArgs = @('"' + $src + '"', '"' + $ClientDir + '"', '/E', '/IS', '/IT', '/R:2', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/NP') $copy = Start-Process -FilePath 'robocopy.exe' -ArgumentList $copyArgs -PassThru -WindowStyle Hidden $lastCopyStatus = Get-Date while (-not $copy.HasExited) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 if ((Get-Date) -ge $lastCopyStatus.AddSeconds(3)) { Set-Status 'Instaluje pliki... prosze czekac.' 96 $lastCopyStatus = Get-Date } } $rc = $copy.ExitCode if ($rc -ge 8) { throw ('robocopy error ' + $rc) } foreach ($name in $backup.Keys) { [IO.File]::WriteAllBytes((Join-Path $ClientDir $name), $backup[$name]) } Save-LocalVersion $remoteVer $wantSha Set-Status 'Finalizuje aktualizacje...' 99 foreach ($name in @('pokeone_DX9.exe', 'pokeone_OpenGL.exe', 'LAUNCHER.ps1', 'UPDATE.ps1', 'PokeOne.bat', 'Start_PokeOne.vbs')) { $path = Join-Path $ClientDir $name if (Test-Path -LiteralPath $path) { Unblock-File -LiteralPath $path -ErrorAction SilentlyContinue } [System.Windows.Forms.Application]::DoEvents() } Set-Status ('Update OK (' + $remoteVer + ').') 100 } finally { if (Test-Path -LiteralPath $tmpRoot) { $safeParent = $tmpParent.TrimEnd('\') + '\' if (-not [IO.Path]::GetFullPath($tmpRoot).StartsWith($safeParent, [StringComparison]::OrdinalIgnoreCase)) { throw 'Niebezpieczna sciezka sprzatania aktualizacji.' } Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue } } } function Test-UpdateNeeded { $localVer = Get-LocalVersion try { $remote = Read-RemoteJson $VersionUrl } catch { Write-Log ('FromClient: brak netu - skip (' + $_.Exception.Message + ')') return $false } $remoteVer = [string]$remote.version if ($Force) { return $true } if (-not $localVer -or $localVer -eq '0') { return $true } return ($remoteVer -ne $localVer) } # --- main --- try { if ($FromClient) { # Running from in-game start: only act when a newer package exists. if (-not (Test-UpdateNeeded)) { Write-Log ('FromClient: klient aktualny (' + (Get-LocalVersion) + ').') exit 0 } # Need update — show progress window, replace files, restart game. $Silent = $false $script:Ui = New-LauncherUi Set-Status 'Wykryto nowa wersje klienta - aktualizuje...' Invoke-UpdateIfNeeded Start-Game Start-Sleep -Milliseconds 600 exit 0 } if (-not $Silent) { $script:Ui = New-LauncherUi } Invoke-UpdateIfNeeded Start-Game Start-Sleep -Milliseconds 600 } catch { Write-Log ('BLAD: ' + $_.Exception.Message) Set-Status ('Update nieudany. ' + $_.Exception.Message) if (-not $FromClient -or $script:StoppedGameForUpdate) { try { Start-Game } catch {} } if (-not $Silent) { $fallback = if ($FromClient -and -not $script:StoppedGameForUpdate) { 'Stary klient pozostaje uruchomiony.' } else { 'Uruchamiam lokalna wersje.' } [System.Windows.Forms.MessageBox]::Show( ('Problem z update: ' + $_.Exception.Message + "`n`n" + $fallback), 'PokeOne' ) } } finally { if ($script:Ui -and $script:Ui.Form) { $script:Ui.Form.Close() $script:Ui.Form.Dispose() } } exit 0