XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWarframe

【WinUI】基于Warframe游戏本体的公开服务器以及安装器

公开
关注 0 Fork 0 Star 0
UTF-8
#requires -Version 7.0
param([Parameter(Mandatory)][string]$Package, [switch]$NoWait, [switch]$AbilityBanks)
$ErrorActionPreference = 'Stop'
$taskRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$taskPackage = [IO.Path]::GetFullPath($Package)
$taskRun = Join-Path $taskRoot $(if ($AbilityBanks) { 'artifacts/ability-bank-game-test' } else { 'artifacts/multi-ability-game-test' })
$taskGame = 'D:\Warframe'
$taskOrigin = 'http://127.0.0.1:18444'
$taskRuntime = Join-Path $taskRun 'runtime'
$taskInstaller = Join-Path $taskPackage 'Client/Installer/XFEWarframe.Installer.exe'
$taskServerExe = Join-Path $taskPackage 'Server/XFEWarframe.Server.exe'
$taskServer = $null; $taskKeepServer = $false
New-Item -ItemType Directory -Path $taskRun -Force | Out-Null
try {
    if (Get-Process -Name Warframe.x64 -ErrorAction SilentlyContinue) { throw 'Close the running game before testing.' }
    foreach ($taskPort in @(18443,18444)) {
        $taskProbe = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,$taskPort)
        try { $taskProbe.Start() } finally { $taskProbe.Stop() }
    }
    $taskCompatibility = (& $taskInstaller check --game $taskGame) | ConvertFrom-Json
    if ($LASTEXITCODE -ne 0 -or -not $taskCompatibility.ExperimentalMultiAbilities) { throw 'This copied game does not match the experimental ABI profile.' }
    & $taskInstaller selftest --payload (Join-Path $taskPackage 'Client/Bridge')
    if ($LASTEXITCODE -ne 0) { throw 'Native callback self-test failed.' }
    $taskServer = Start-Process -FilePath $taskServerExe -WorkingDirectory (Split-Path $taskServerExe) -ArgumentList @('"--root=' + $taskRuntime + '"','--port=18444','--game-port=18443','--no-irc') -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $taskRun 'server-stdout.log') -RedirectStandardError (Join-Path $taskRun 'server-stderr.log')
    $taskReady = $false
    for ($taskAttempt = 0; $taskAttempt -lt 80; $taskAttempt++) {
        if ($taskServer.HasExited) { throw 'The isolated server exited.' }
        try { $null = Invoke-RestMethod "$taskOrigin/health" -TimeoutSec 1; $taskReady = $true; break } catch { Start-Sleep -Milliseconds 250 }
    }
    if (-not $taskReady) { throw 'The isolated server did not become ready.' }
    $taskConfig = Get-Content -LiteralPath (Join-Path $taskRuntime 'server.json') -Raw | ConvertFrom-Json
    $taskHeaders = @{ Authorization = 'Bearer ' + $taskConfig.AdminToken }
    function Send-TestBody($Url, $Body) {
        Invoke-RestMethod $Url -Headers $taskHeaders -Method Post -ContentType 'application/json' -Body ([Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Depth 90 -Compress)))
    }
    $taskLogin = @{ email='multi-ability@example.invalid'; password='LocalAbilityCheck!2026' }
    try { $null = Send-TestBody "$taskOrigin/control/v1/accounts" ($taskLogin + @{ displayName='MultiAbilityCheck' }) }
    catch { if ([int]$_.Exception.Response.StatusCode -ne 409) { throw } }
    $taskSession = Invoke-RestMethod "$taskOrigin/player/v1/login" -Method Post -ContentType 'application/json' -Body ($taskLogin | ConvertTo-Json -Compress)
    $taskAccount = $taskSession.profile.id
    $taskAccountUrl = "$taskOrigin/control/v1/accounts/$taskAccount"
    $null = Send-TestBody "$taskAccountUrl/kick" @{}
    $null = Send-TestBody "$taskAccountUrl/tutorial" @{ skipTutorial=$true }
    $taskInventory = Invoke-RestMethod "$taskAccountUrl/export" -Headers $taskHeaders
    # Only the dedicated local test account is reset. No production inventory is imported.
    $taskSuitType = if ($AbilityBanks) { '/Lotus/Powersuits/Infestation/Infestation' } else { '/Lotus/Powersuits/Excalibur/Excalibur' }
    if (-not ($taskInventory.Suits | Where-Object ItemType -EQ $taskSuitType)) {
        $null = Send-TestBody "$taskAccountUrl/item" @{ type=$taskSuitType; count=1 }
        $taskInventory = Invoke-RestMethod "$taskAccountUrl/export" -Headers $taskHeaders
    }
    if ($AbilityBanks -and -not ($taskInventory.Upgrades | Where-Object ItemType -EQ '/Lotus/Upgrades/XFEWarframe/InfestationKing')) {
        $null = Send-TestBody "$taskAccountUrl/item" @{type='/Lotus/Upgrades/XFEWarframe/InfestationKing';count=1}
        $taskInventory = Invoke-RestMethod "$taskAccountUrl/export" -Headers $taskHeaders
    }
    $taskSuit = @($taskInventory.Suits | Where-Object ItemType -EQ $taskSuitType)[0]
    $taskSuitId = $taskSuit.ItemId.'$oid'
    $taskSuit.XP = 900000
    $taskSuit | Add-Member -NotePropertyName Configs -NotePropertyValue @(@{Name='A - Shock + Roar'},@{Name='B - Original'},@{Name='C - Four skills'}) -Force
    if ($AbilityBanks) {
        $taskSuit.Configs[0].Name = 'A - Twelve choices'
        $taskSuit.Configs[2].Name = 'C - Jade flight'
        $taskSuit | Add-Member -NotePropertyName ModSlotPurchases -NotePropertyValue 3 -Force
        $taskSuit.Configs += @{Name='D - Two choices and Helminth'},@{Name='E - Single two-choice Stalker bank'},@{Name='F - Single three-choice Stalker bank'}
        $taskKingCard = @($taskInventory.Upgrades | Where-Object ItemType -EQ '/Lotus/Upgrades/XFEWarframe/InfestationKing')[0]
        $taskSuit.Configs[1].Upgrades = @($taskKingCard.ItemId.'$oid') + @('') * 10
        if ($taskInventory.Suits | Where-Object ItemType -EQ '/Lotus/Powersuits/Choir/Choir') {
            throw 'Use a clean ability-bank test account without Jade to verify automatic donor dependencies.'
        }
    }
    $taskInventory.LoadOutPresets.NORMAL[0].s | Add-Member -NotePropertyName ItemId -NotePropertyValue @{ '$oid'=$taskSuitId } -Force
    $taskInventory.LoadOutPresets.NORMAL[0].s | Add-Member -NotePropertyName mod -NotePropertyValue 0 -Force
    $taskInventory.LoadOutPresets.NORMAL[0].s | Add-Member -NotePropertyName cus -NotePropertyValue 0 -Force
    $null = Send-TestBody "$taskAccountUrl/import" @{ inventory=$taskInventory }
    $taskPlans = @{
        'ability.multiple.0' = @{ability1='/Lotus/Powersuits/Volt/Abilities/ShockAbility'; ability2='/Lotus/Powersuits/Rhino/Abilities/RhinoRoarAbility'; ability3=''; ability4=''}
        'ability.multiple.2' = @{ability1='/Lotus/Powersuits/Volt/Abilities/ShockAbility'; ability2='/Lotus/Powersuits/Mag/Abilities/PullAbility'; ability3='/Lotus/Powersuits/Rhino/Abilities/IronSkinAbility'; ability4='/Lotus/Powersuits/Rhino/Abilities/RhinoRoarAbility'}
    }
    if ($AbilityBanks) {
        $shock = '/Lotus/Powersuits/Volt/Abilities/ShockAbility'
        $roar = '/Lotus/Powersuits/Rhino/Abilities/RhinoRoarAbility'
        $pull = '/Lotus/Powersuits/Mag/Abilities/PullAbility'
        $iron = '/Lotus/Powersuits/Rhino/Abilities/IronSkinAbility'
        $taskPlans = @{
            'ability.multiple.0' = @{ability1=$shock; ability1_2=$roar; ability1_3=$pull; ability2=$pull; ability2_2=$shock; ability2_3=$roar; ability3=$iron; ability3_2=$shock; ability3_3=$roar; ability4=$iron; ability4_2=$shock; ability4_3=$roar}
            'ability.multiple.2' = @{ability4='/Lotus/Powersuits/Choir/Abilities/ChoirEruptAbility'; ability4_2=$roar; ability4_3=$iron}
            'ability.multiple.3' = @{ability1=$shock; ability1_2='/Lotus/Powersuits/PowersuitAbilities/HelminthHackAbility'}
            'ability.multiple.4' = @{ability4='/Lotus/Powersuits/Stalker/Abilities/StalkerAbsorbAbility'; ability4_2='/Lotus/Powersuits/Stalker/Abilities/StalkerSmokeScreenAbility'}
            'ability.multiple.5' = @{ability1='/Lotus/Powersuits/Stalker/Abilities/StalkerAbsorbAbility'; ability1_2='/Lotus/Powersuits/Stalker/Abilities/StalkerSmokeScreenAbility'; ability1_3=$roar}
        }
    }
    foreach ($taskAction in $taskPlans.Keys) {
        $taskView = Invoke-RestMethod "$taskAccountUrl/editor?category=Suits&item=$taskSuitId" -Headers $taskHeaders
        $taskValues = @{}
        foreach ($field in ($taskView.actions | Where-Object key -EQ $taskAction).fields) { $taskValues[$field.key] = '' }
        foreach ($key in $taskPlans[$taskAction].Keys) { $taskValues[$key] = $taskPlans[$taskAction][$key] }
        $null = Send-TestBody "$taskAccountUrl/editor" @{version=$taskView.version; category='Suits'; item=$taskSuitId; action=$taskAction; values=$taskValues}
    }
    $taskSaved = Invoke-RestMethod "$taskAccountUrl/export" -Headers $taskHeaders
    $taskSavedSuit = @($taskSaved.Suits | Where-Object { $_.ItemId.'$oid' -eq $taskSuitId })[0]
    $taskExpectedOverrides = if ($AbilityBanks) { 4 } else { 2 }
    if (@($taskSavedSuit.Configs[0].XFEAbilityOverrides.PSObject.Properties).Count -ne $taskExpectedOverrides -or $taskSavedSuit.Configs[1].XFEAbilityOverrides) { throw 'Configuration isolation check failed.' }
    $taskInstalled = Get-Content -LiteralPath (Join-Path $taskGame 'XFEWarframe/client.json') -Raw | ConvertFrom-Json
    & $taskInstaller repair --game $taskGame --server $taskInstalled.ServerUrl --payload (Join-Path $taskPackage 'Client/Bridge')
    if ($LASTEXITCODE -ne 0) { throw 'Updating the copied game component failed.' }
    Write-Host 'The engine test adapter will log in to the isolated test account automatically.'
    if ($AbilityBanks) { Write-Host 'Nidus: A = four banks of three choices; B = original skills; C = Jade flight, Roar, Iron Skin.' }
    else { Write-Host 'Excalibur: A = Shock + Roar; B = original skills; C = Shock, Pull, Iron Skin, Roar.' }
    $taskLaunch = & $taskInstaller launch --game $taskGame --language zh --server $taskOrigin --game-test-control 2>&1
    $taskLaunch | Tee-Object -FilePath (Join-Path $taskRun 'launch.log')
    if ($LASTEXITCODE -ne 0) { throw 'Starting the copied game failed.' }
    $taskMatch = [regex]::Match(($taskLaunch -join "`n"), 'Game PID: (\d+)')
    if (-not $taskMatch.Success) { throw 'The launcher did not return a game process.' }
    $taskGameProcess = Get-Process -Id ([int]$taskMatch.Groups[1].Value)
    @{serverPid=$taskServer.Id; gamePid=$taskGameProcess.Id; origin=$taskOrigin; runtime=$taskRuntime; accountId=$taskAccount; suitId=$taskSuitId; package=$taskPackage} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $taskRun 'session.json') -Encoding utf8
    if ($AbilityBanks) {
        & (Join-Path $PSScriptRoot 'Test-GameBranding.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'branding-login-validation') -BeforeLogin
    }
    & (Join-Path $PSScriptRoot 'Test-MultiAbilityGame.ps1') -GameDirectory $taskGame -SessionFile (Join-Path $taskRun 'session.json') -CryptoAssembly (Join-Path $taskPackage 'Server/BouncyCastle.Cryptography.dll') -ReportDirectory (Join-Path $taskRun 'login-validation') -LoginOnly:$AbilityBanks
    if ($AbilityBanks) {
        & (Join-Path $PSScriptRoot 'Test-GameBranding.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'branding-game-validation')
        & (Join-Path $PSScriptRoot 'Test-AbilityBanksGame.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'engine-validation')
        & (Join-Path $PSScriptRoot 'Test-AbilityWheelsGame.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'wheel-validation')
        & (Join-Path $PSScriptRoot 'Test-AbilityAudioGame.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'audio-validation')
        & (Join-Path $PSScriptRoot 'Test-InfestationKingGame.ps1') -SessionFile (Join-Path $taskRun 'session.json') -ReportDirectory (Join-Path $taskRun 'king-validation')
    }
    if ($NoWait) { $taskKeepServer=$true; Write-Output "Test session: $taskRun/session.json" }
    else { Write-Host 'Keep this window open while testing.'; $taskGameProcess.WaitForExit() }
} finally {
    if ($taskServer -and -not $taskKeepServer -and -not $taskServer.HasExited) { Stop-Process -Id $taskServer.Id }
    if ($taskServer) { $taskServer.Dispose() }
}