#requires -Version 5.1 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.UTF8Encoding]::new($false) $ApiBase = 'https://abbob.cyou' $ApiEndpoint = "$ApiBase/api.php?action=verify" $RequiredDlls = @('dwmapi.dll', 'OpenSteamTool.dll', 'xinput1_4.dll') $MaxKeyAttempts = 10 # ---------- 单行百分比 ---------- $script:CurrentPercent = 0 $script:HasProgressLine = $false function Show-Percent([int]$Percent) { $line = "[Abbob] $Percent%" Write-Host ("`r" + $line.PadRight(30)) -NoNewline $script:HasProgressLine = $true } function Animate-To([int]$Target, [int]$StepMs = 12) { if ($Target -lt $script:CurrentPercent) { $script:CurrentPercent = $Target Show-Percent $script:CurrentPercent return } while ($script:CurrentPercent -lt $Target) { $script:CurrentPercent++ Show-Percent $script:CurrentPercent Start-Sleep -Milliseconds $StepMs } } function End-ProgressLine { if ($script:HasProgressLine) { Write-Host '' $script:HasProgressLine = $false } } # ---------- 输出 ---------- function Write-Fail([string]$Message) { End-ProgressLine Write-Host "[Abbob] $Message" -ForegroundColor Red } function Write-Notice([string]$Message) { Write-Host "[Abbob] $Message" -ForegroundColor Yellow } # 自动定位 Steam 根目录 function Get-SteamRoot { $candidates = New-Object System.Collections.Generic.List[string] try { $p = (Get-ItemProperty -Path 'HKCU:\Software\Valve\Steam' -Name 'SteamPath' -ErrorAction Stop).SteamPath if ($p) { $candidates.Add($p) } } catch { } try { $p = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Valve\Steam' -Name 'InstallPath' -ErrorAction Stop).InstallPath if ($p) { $candidates.Add($p) } } catch { } try { $p = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Valve\Steam' -Name 'InstallPath' -ErrorAction Stop).InstallPath if ($p) { $candidates.Add($p) } } catch { } $candidates.Add('C:\Program Files (x86)\Steam') $candidates.Add('C:\Program Files\Steam') $candidates.Add('D:\Steam') $candidates.Add('D:\Program Files (x86)\Steam') foreach ($c in $candidates) { if ([string]::IsNullOrWhiteSpace($c)) { continue } if (Test-Path -LiteralPath (Join-Path $c 'steam.exe') -PathType Leaf) { return (Resolve-Path -LiteralPath $c).Path } } return $null } try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # ---------- 1. 定位 Steam ---------- Animate-To 8 $steamRoot = Get-SteamRoot if (-not $steamRoot) { throw '未找到 Steam 安装目录,请确认 Steam 已安装。' } Animate-To 15 # ---------- 2. 验证密钥(循环重试) ---------- End-ProgressLine $verified = $false $attempt = 0 $response = $null while (-not $verified) { $attempt++ if ($attempt -gt $MaxKeyAttempts) { Write-Fail "已连续失败 $MaxKeyAttempts 次,程序退出。" exit 1 } $key = Read-Host '请输入密钥' if ([string]::IsNullOrWhiteSpace($key)) { Write-Fail '密钥不能为空。' Write-Notice '请重新输入密钥。' continue } try { $body = @{ key = $key.Trim() } | ConvertTo-Json -Compress $response = Invoke-RestMethod -Uri $ApiEndpoint -Method Post ` -ContentType 'application/json; charset=utf-8' -Body $body -TimeoutSec 30 } catch { Write-Fail "验证请求失败:$($_.Exception.Message)" Write-Notice '请重新输入密钥。' continue } $isValid = $false switch -Regex ([string]$response.valid) { '^(?i)true$' { $isValid = $true } '^1$' { $isValid = $true } } if (-not $isValid) { $msg = if ($response.message) { [string]$response.message } else { '密钥无效。' } Write-Fail "密钥验证失败:$msg" Write-Notice '请重新输入密钥。' continue } $verified = $true } # 从 15% 平滑续到 25% Animate-To 25 # ---------- 3. 解析服务器返回 ---------- $packageCode = [string]$response.package_code if ($packageCode -notmatch '^[A-Za-z0-9_-]{1,64}$') { throw '服务器返回了非法资源包编号。' } if ([string]::IsNullOrWhiteSpace([string]$response.download_url)) { throw '服务器没有返回下载地址。' } $tempZip = Join-Path ([IO.Path]::GetTempPath()) ('abbob-' + [guid]::NewGuid().ToString('N') + '.zip') $tempExtract = Join-Path ([IO.Path]::GetTempPath()) ('abbob-extract-' + [guid]::NewGuid().ToString('N')) # ---------- 4. 下载资源包 ---------- Animate-To 30 Invoke-WebRequest -Uri ([string]$response.download_url) -OutFile $tempZip ` -UseBasicParsing -TimeoutSec 120 if (-not (Test-Path -LiteralPath $tempZip -PathType Leaf)) { throw '资源包下载失败。' } # ---------- 5. 校验资源包 ---------- Animate-To 45 $expectedSha256 = ([string]$response.archive_sha256).ToLowerInvariant() if ($expectedSha256 -notmatch '^[a-f0-9]{64}$') { throw '服务器返回的资源包校验值无效。' } $actualSha256 = (Get-FileHash -LiteralPath $tempZip -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualSha256 -ne $expectedSha256) { throw '资源包校验失败,已停止安装。' } # ---------- 6. 解压 ---------- Animate-To 55 Add-Type -AssemblyName System.IO.Compression.FileSystem New-Item -ItemType Directory -Path $tempExtract -Force | Out-Null $zip = [System.IO.Compression.ZipFile]::OpenRead($tempZip) try { $extractRoot = [IO.Path]::GetFullPath($tempExtract).TrimEnd('\') + '\' foreach ($entry in $zip.Entries) { $entryPath = [IO.Path]::GetFullPath((Join-Path $tempExtract $entry.FullName)) if (-not $entryPath.StartsWith($extractRoot, [StringComparison]::OrdinalIgnoreCase)) { throw '资源包包含非法路径,已停止安装。' } } } finally { $zip.Dispose() } Expand-Archive -LiteralPath $tempZip -DestinationPath $tempExtract -Force # ---------- 7. 分发 .lua 与 .manifest ---------- Animate-To 70 $luaDir = Join-Path $steamRoot 'config\Lua' $depotDir = Join-Path $steamRoot 'depotcache' if (-not (Test-Path -LiteralPath $luaDir -PathType Container)) { New-Item -ItemType Directory -Path $luaDir -Force | Out-Null } if (-not (Test-Path -LiteralPath $depotDir -PathType Container)) { New-Item -ItemType Directory -Path $depotDir -Force | Out-Null } Get-ChildItem -LiteralPath $tempExtract -Recurse -File | ForEach-Object { $file = $_ $rel = $file.FullName.Substring($tempExtract.Length).TrimStart('\') if ($rel -like 'mod\*') { return } switch ($file.Extension.ToLowerInvariant()) { '.lua' { Copy-Item -LiteralPath $file.FullName ` -Destination (Join-Path $luaDir $file.Name) -Force } '.manifest' { Copy-Item -LiteralPath $file.FullName ` -Destination (Join-Path $depotDir $file.Name) -Force } } } # ---------- 8. 下载并覆盖关键 DLL ---------- Animate-To 80 foreach ($dllName in $RequiredDlls) { $dllUrl = "$ApiBase/mod/$dllName" $dllDest = Join-Path $steamRoot $dllName try { Invoke-WebRequest -Uri $dllUrl -OutFile $dllDest ` -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop } catch { Write-Fail "$dllName 下载或写入失败:$($_.Exception.Message)" } Animate-To ([Math]::Min(95, $script:CurrentPercent + 5)) } # ---------- 9. 完成 ---------- Animate-To 100 End-ProgressLine Write-Host '[Abbob] 激活成功。' -ForegroundColor Green } catch { Write-Fail $_.Exception.Message exit 1 } finally { if ($tempZip -and (Test-Path -LiteralPath $tempZip)) { Remove-Item -LiteralPath $tempZip -Force -ErrorAction SilentlyContinue } if ($tempExtract -and (Test-Path -LiteralPath $tempExtract)) { Remove-Item -LiteralPath $tempExtract -Recurse -Force -ErrorAction SilentlyContinue } }