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

XFEWarframe

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

公开
关注 0 Fork 0 Star 0
UTF-8
#requires -Version 7.0
param([ValidateSet('Debug','Release')][string]$Configuration = 'Release', [string]$Package, [switch]$UseLaunchProfiles, [switch]$NoBuild, [switch]$KeepOpen, [switch]$VerifyDialogs, [ValidateSet('zh','en','de','es','fr','pl','ru','uk')][string]$UiLanguage = 'zh', [string]$NativeCardManifest)
$ErrorActionPreference = 'Stop'
$taskRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$taskRun = Join-Path $taskRoot ('artifacts/desktop-smoke-' + [Guid]::NewGuid().ToString('N'))
$taskIntermediate = Join-Path $taskRoot ('artifacts/test-obj/' + [Guid]::NewGuid().ToString('N').Substring(0, 12))
$taskRuntime = Join-Path $taskRun 'Server/runtime'
New-Item -ItemType Directory -Path $taskRuntime -Force | Out-Null
$taskLaunchProfiles = @{}
$taskVisibleProcesses = @()
$taskCompleted = $false
if ($UseLaunchProfiles -and $Package) { throw 'Choose source launch profiles or a published package, not both.' }
if ($NoBuild -and -not $UseLaunchProfiles) { throw '-NoBuild requires -UseLaunchProfiles.' }
if ($Package) {
    $taskServerExe = Join-Path $Package 'Server/XFEWarframe.Server.exe'
    $taskAdminExe = Join-Path $Package 'Admin/XFEWarframe.Admin.exe'
    $taskPlayerExe = Join-Path $Package 'Client/XFEWarframe.Player.exe'
} elseif ($UseLaunchProfiles) {
    if (-not $NoBuild) {
        dotnet build (Join-Path $taskRoot 'XFEWarframe.slnx') -c $Configuration -p:Platform=x64 --nologo -v quiet
        if ($LASTEXITCODE -ne 0) { throw "Solution $Configuration/x64 build failed" }
    }
    foreach ($taskSpec in @(
        @{Role='Server';Project='Server/XFEWarframe.Server';Platform='AnyCPU'},
        @{Role='Admin';Project='Admin/XFEWarframe.Admin';Platform='x64'},
        @{Role='Player';Project='Client/XFEWarframe.Player';Platform='x64'}
    )) {
        $taskProjectDirectory = Join-Path $taskRoot $taskSpec.Project
        $taskName = Split-Path $taskSpec.Project -Leaf
        $taskPropertiesText = dotnet msbuild (Join-Path $taskProjectDirectory "$taskName.csproj") "-p:Configuration=$Configuration" "-p:Platform=$($taskSpec.Platform)" -getProperty:RunCommand,RunWorkingDirectory,ActiveDebugProfile,PlatformTarget,WindowsPackageType -nologo
        if ($LASTEXITCODE -ne 0) { throw "Cannot evaluate $taskName debug target" }
        $taskProperties = ($taskPropertiesText | ConvertFrom-Json).Properties
        $taskProfiles = (Get-Content (Join-Path $taskProjectDirectory 'Properties/launchSettings.json') -Raw | ConvertFrom-Json -AsHashtable).profiles
        $taskProfileName = if ($taskProperties.ActiveDebugProfile) { $taskProperties.ActiveDebugProfile } else { @($taskProfiles.Keys)[0] }
        $taskProfile = $taskProfiles[$taskProfileName]
        if ($taskProfile.commandName -ne 'Project') { throw "$taskName is not configured to launch its executable" }
        if (-not (Test-Path -LiteralPath $taskProperties.RunCommand -PathType Leaf)) { throw "Debug executable missing: $($taskProperties.RunCommand)" }
        if ($taskSpec.Role -ne 'Server' -and ($taskProperties.PlatformTarget -ne 'x64' -or $taskProperties.WindowsPackageType -ne 'None')) { throw 'WinUI must launch unpackaged on x64' }
        if ($taskSpec.Role -in @('Server','Admin')) {
            if ($taskProfile.commandLineArgs -notmatch '--root=([^ ]+)') { throw 'Missing server runtime argument' }
            $taskConfiguredRoot = [IO.Path]::GetFullPath((Join-Path $taskProperties.RunWorkingDirectory $Matches[1]))
            if ($taskConfiguredRoot -ne (Join-Path $taskRoot 'Server/runtime')) { throw 'Admin and Server debug profiles must use the same runtime directory' }
        } else {
            if ($taskProfile.commandLineArgs -notmatch '--payload=([^ ]+)') { throw 'Missing player payload argument' }
            $taskPayloadRoot = [IO.Path]::GetFullPath((Join-Path $taskProperties.RunWorkingDirectory $Matches[1]))
            if ($taskPayloadRoot -ne (Join-Path $taskRoot 'artifacts/bridge')) { throw 'Incorrect developer bridge path' }
        }
        $taskLaunchProfiles[$taskSpec.Role] = @{Executable=$taskProperties.RunCommand; WorkingDirectory=$taskProperties.RunWorkingDirectory; Arguments=$taskProfile.commandLineArgs; Profile=$taskProfileName}
    }
    $taskServerExe = $taskLaunchProfiles.Server.Executable
    $taskAdminExe = $taskLaunchProfiles.Admin.Executable
    $taskPlayerExe = $taskLaunchProfiles.Player.Executable
    $taskLaunchProfiles | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $taskRun 'launch-profiles.json') -Encoding utf8
} else {
    foreach ($taskProject in @('Server/XFEWarframe.Server','Admin/XFEWarframe.Admin','Client/XFEWarframe.Player')) {
        $taskName = Split-Path $taskProject -Leaf
        $taskBuildOutput = Join-Path $taskRun "build/$taskName"
        dotnet build (Join-Path $taskRoot "$taskProject/$taskName.csproj") -c $Configuration ("-p:XFEIntermediateRoot=" + $taskIntermediate) ("-p:OutputPath=" + $taskBuildOutput + [IO.Path]::DirectorySeparatorChar) --nologo -v quiet
        if ($LASTEXITCODE -ne 0) { throw "Build failed: $taskName" }
    }
    $taskServerExe = Join-Path $taskRun 'build/XFEWarframe.Server/XFEWarframe.Server.exe'
    $taskAdminExe = Join-Path $taskRun 'build/XFEWarframe.Admin/XFEWarframe.Admin.exe'
    $taskPlayerExe = Join-Path $taskRun 'build/XFEWarframe.Player/XFEWarframe.Player.exe'
}
$taskProbe = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0)
$taskProbe.Start()
$taskPort = $taskProbe.LocalEndpoint.Port
$taskProbe.Stop()
$taskProbe.Start()
$taskGamePort = $taskProbe.LocalEndpoint.Port
$taskProbe.Stop()
$taskOrigin = "http://127.0.0.1:$taskPort"
$taskServerArguments = @('"--root=' + $taskRuntime + '"', "--port=$taskPort", "--game-port=$taskGamePort", '--no-irc')
$taskServerDirectory = Split-Path $taskServerExe
if ($UseLaunchProfiles) {
    # The isolated root comes first so the profile cannot touch production state.
    $taskServerArguments += $taskLaunchProfiles.Server.Arguments
    $taskServerDirectory = $taskLaunchProfiles.Server.WorkingDirectory
}
$taskServerProcess = Start-Process -FilePath $taskServerExe -WorkingDirectory $taskServerDirectory -ArgumentList $taskServerArguments -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $taskRun 'server-stdout.log') -RedirectStandardError (Join-Path $taskRun 'server-stderr.log')
try {
    $taskReady = $false
    for ($i = 0; $i -lt 60; $i++) {
        if ($taskServerProcess.HasExited) { throw 'Isolated server exited before readiness' }
        try { $null = Invoke-RestMethod "$taskOrigin/health" -TimeoutSec 1; $taskReady = $true; break } catch { Start-Sleep -Milliseconds 250 }
    }
    if (-not $taskReady) { throw 'Isolated server was not ready' }
    $taskConfigPath = Join-Path $taskRuntime 'server.json'
    $taskConfig = Get-Content -LiteralPath $taskConfigPath -Raw | ConvertFrom-Json
    # --port is an in-memory override; write the test copy so Admin can discover this ephemeral listener.
    $taskConfig.ListenUrls = @("$taskOrigin/")
    $taskConfig.PublicUrl = $taskOrigin
    $taskConfig | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $taskConfigPath -Encoding utf8
    $taskHeaders = @{ Authorization = 'Bearer ' + $taskConfig.AdminToken }
    $taskBody = @{ displayName = 'UI Verification'; email = 'ui@example.invalid'; password = [Guid]::NewGuid().ToString('N') } | ConvertTo-Json
    $null = Invoke-RestMethod "$taskOrigin/control/v1/accounts" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body $taskBody
    $taskPermissions = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Headers $taskHeaders
    $taskAllowed = @{ experience=$true; 'mod.rank'=$true; riven=$true; batch=$true; quest=$true; 'create.quest'=$true; 'archive.export'=$true; 'archive.import'=$true; 'inbox.read'=$true; syndicate=$true }
    foreach ($taskField in $taskPermissions.fields | Where-Object { $_.key.StartsWith('unlock.', [StringComparison]::Ordinal) }) { $taskAllowed[$taskField.key] = $true }
    $taskEditorBody = @{ version = $taskPermissions.version; values = $taskAllowed } | ConvertTo-Json -Depth 6
    $null = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body $taskEditorBody
    foreach ($taskRole in @('Admin','Player')) {
        $taskExe = if ($taskRole -eq 'Admin') { $taskAdminExe } else { $taskPlayerExe }
        $taskReport = Join-Path $taskRun "$taskRole.json"
        $taskProfile = Join-Path $taskRun "$taskRole/ProfilesRoot"
        $taskArguments = @('"--verify-ui=' + $taskReport + '"', '"--profile-root=' + $taskProfile + '"', '"--root=' + $taskRuntime + '"')
        $taskArguments += "--verify-api=$taskOrigin", "--ui-language=$UiLanguage"
        if ($NativeCardManifest) { $taskArguments += ('"--verify-native-card-manifest=' + [IO.Path]::GetFullPath($NativeCardManifest) + '"') }
        if ($KeepOpen) { $taskArguments += '--verify-visible=true' }
        if ($VerifyDialogs) { $taskArguments += '--verify-dialogs=true' }
        $taskWorkingDirectory = Split-Path $taskExe
        if ($UseLaunchProfiles) {
            $taskArguments += $taskLaunchProfiles[$taskRole].Arguments
            $taskWorkingDirectory = $taskLaunchProfiles[$taskRole].WorkingDirectory
        }
        $taskUiProcess = Start-Process -FilePath $taskExe -WorkingDirectory $taskWorkingDirectory -ArgumentList $taskArguments -WindowStyle Hidden -PassThru
        $taskUiAccepted = $false
        try {
            if ($KeepOpen) {
                $taskDeadline = [DateTime]::UtcNow.AddSeconds(60)
                while (-not (Test-Path -LiteralPath $taskReport) -and -not $taskUiProcess.HasExited -and [DateTime]::UtcNow -lt $taskDeadline) { Start-Sleep -Milliseconds 200 }
            } elseif (-not $taskUiProcess.WaitForExit(90000)) { throw "$taskRole UI verification timed out" }
            if (-not (Test-Path -LiteralPath $taskReport)) { throw "$taskRole did not create a verification report (exit $($taskUiProcess.ExitCode))" }
            $taskResult = Get-Content -LiteralPath $taskReport -Raw | ConvertFrom-Json
            if (-not $taskResult.passed) { throw "$taskRole verification failed: $($taskResult.error)" }
            if ($KeepOpen) { $taskVisibleProcesses += @{ role = $taskRole; pid = $taskUiProcess.Id; path = $taskExe; profile = $taskProfile } }
            $taskUiAccepted = $true
            Write-Output "PASS $taskRole : $($taskResult.checks.Count) integration checks"
        } finally {
            if ((-not $KeepOpen -or -not $taskUiAccepted) -and -not $taskUiProcess.HasExited) { Stop-Process -Id $taskUiProcess.Id }
            $taskUiProcess.Dispose()
        }
    }
    # Exercise the same native inventory view with genuinely revoked server permissions.
    $taskSavedPermissions = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Headers $taskHeaders
    $taskDisabledValues = @{}
    $taskSavedValues = @{}
    foreach ($taskValue in $taskSavedPermissions.fields) { $taskDisabledValues[$taskValue.key] = $false; $taskSavedValues[$taskValue.key] = [bool]$taskValue.value }
    $taskDisableBody = @{version=$taskSavedPermissions.version; values=$taskDisabledValues} | ConvertTo-Json -Depth 8
    $null = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body $taskDisableBody
    $null = Invoke-RestMethod "$taskOrigin/control/v1/config" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body '{"allowPlayerCurrencyGrant":false,"allowPlayerItemGrant":false}'
    $taskReaderReport = Join-Path $taskRun 'PlayerReadOnly.json'
    $taskReaderProfile = Join-Path $taskRun 'PlayerReadOnly/ProfilesRoot'
    $taskReader = Start-Process -FilePath $taskPlayerExe -WorkingDirectory (Split-Path $taskPlayerExe) -ArgumentList @('"--verify-ui=' + $taskReaderReport + '"', '"--profile-root=' + $taskReaderProfile + '"', "--verify-api=$taskOrigin", '--verify-readonly=true', "--ui-language=$UiLanguage") -WindowStyle Hidden -PassThru
    try {
        if (-not $taskReader.WaitForExit(90000)) { throw 'Read-only inventory verification timed out' }
        $taskReaderResult = Get-Content -LiteralPath $taskReaderReport -Raw | ConvertFrom-Json
        if (-not $taskReaderResult.passed) { throw "Read-only inventory verification failed: $($taskReaderResult.error)" }
        Write-Output 'PASS PlayerReadOnly : disabled permissions and inventory category navigation'
    } finally {
        if (-not $taskReader.HasExited) { Stop-Process -Id $taskReader.Id }
        $taskReader.Dispose()
        $taskCurrentPermissions = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Headers $taskHeaders
        $taskRestoreBody = @{version=$taskCurrentPermissions.version; values=$taskSavedValues} | ConvertTo-Json -Depth 8
        $null = Invoke-RestMethod "$taskOrigin/control/v1/editor/permissions" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body $taskRestoreBody
        $null = Invoke-RestMethod "$taskOrigin/control/v1/config" -Method Post -Headers $taskHeaders -ContentType 'application/json' -Body '{"allowPlayerCurrencyGrant":true,"allowPlayerItemGrant":true}'
    }
    $taskCompleted = $true
    if ($KeepOpen) {
        @{ serverPid = $taskServerProcess.Id; serverPath = $taskServerExe; origin = $taskOrigin; runtime = $taskRuntime; windows = $taskVisibleProcesses } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $taskRun 'interactive-session.json') -Encoding utf8
        Write-Output "Interactive verification: $taskRun/interactive-session.json"
    }
    Write-Output "Desktop reports: $taskRun"
} finally {
    if (-not ($KeepOpen -and $taskCompleted)) {
        foreach ($taskVisible in $taskVisibleProcesses) { Stop-Process -Id $taskVisible.pid -ErrorAction SilentlyContinue }
        if (-not $taskServerProcess.HasExited) { Stop-Process -Id $taskServerProcess.Id }
    }
    $taskServerProcess.Dispose()
}