You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

612 lines
19 KiB

function ForceDesktopSession {
$LoggedInUser = $Env:USERNAME
# Get list of users
$UserSessions = $(quser | ForEach-Object -Process { $_ -replace '\s{2,}',',' } | ConvertFrom-CSV)
ForEach ($UserSession in $UserSessions) {
if ($UserSession.USERNAME -eq "$LoggedInUser" -or
$UserSession.USERNAME -eq ">$LoggedInUser") {
# Found our user; check if we're in a session or not
if ($UserSession.STATE -eq "Disc" -or
$UserSession.ID -eq "Disc") {
# Our session state is disconnected; open up the console
$username = $UserSession.USERNAME;
Write-Host "$LoggedInUser is disconnected; opening console"
tscon $UserSession.SESSIONNAME /dest:console
sleep 10 # Give Windows some time to init the console session
}
break
}
}
}
function PrepRootDir {
Param(
[string]$RootDir
)
# Add this directory to windows defender
Write-Host "Adding Windows Defender exclusion..."
Add-MpPreference -ExclusionPath "$RootDir"
}
function PrepSVN {
Param(
[string]$UdkPath,
[string]$UdkUrl,
[int]$TargetRevision,
[string]$SvnUsername="",
[string]$SvnPassword=""
)
if (![System.IO.Directory]::Exists("$UdkPath")) {
Write-Host "Checking out UDK_Uncooked..."
if (![string]::IsNullOrWhiteSpace($SvnUsername) -and
![string]::IsNullOrWhiteSpace($SvnPassword)) {
svn checkout --non-interactive --trust-server-cert --username $SvnUsername --password $SvnPassword $UdkUrl $UdkPath
}
else {
svn checkout $UdkUrl $UdkPath
}
}
# Revert any local changes
svn revert --recursive $UdkPath
# Update UDK to latest revision
if ($TargetRevision -eq 0) {
svn update $UdkPath
}
else {
svn update -r $TargetRevision $UdkPath
}
}
function GetSvnRevision {
Param(
[string]$SvnPath
)
return (svn info --show-item revision $SvnPath)
}
function GetVersionData {
Param(
[string]$VersionUrl
)
Invoke-WebRequest "$VersionUrl" | Select-Object -ExpandProperty Content | ConvertFrom-Json
}
function GetBranchConfig {
Param(
[string]$BuildPath,
[string]$SourceBranch
)
$ConfigData = (Get-Content "$BuildPath\config.json" | ConvertFrom-Json)
$ConfigBranchData = $ConfigData.branches.$SourceBranch
return $ConfigBranchData
}
function GetTypeConfig {
Param(
[string]$BuildPath,
[string]$BuildType
)
$ConfigData = (Get-Content "$BuildPath\config.json" | ConvertFrom-Json)
$ConfigTypeData = $ConfigData.types.$BuildType
return $ConfigTypeData
}
function GetNodeConfig {
Param(
[string]$BuildPath,
[string]$NodeName
)
$ConfigData = (Get-Content "$BuildPath\config.json" | ConvertFrom-Json)
if ([string]::IsNullOrEmpty($NodeName) -or
!$ConfigData.nodes.$NodeName) {
Write-Host "No config for $NodeName found; using default"
$NodeName = "default"
}
$ConfigNodeData = $ConfigData.nodes.$NodeName
return $ConfigNodeData
}
function GetBuildType {
Param(
[string]$BuildPath,
[string]$SourceBranch,
[string]$BuildType="Auto"
)
if ($BuildType -eq "Auto") {
$BuildType = (GetBranchConfig "$BuildPath" "$SourceBranch").type
if (!$BuildType) {
Write-Host "ERROR: Config not found for branch '$SourceBranch'"
exit 1;
}
}
return $BuildType
}
function OmitCodePackage {
Param(
[string]$UdkPath,
[string]$PackageName
)
$EngineFile = "$UdkPath\UDKGame\Config\DefaultEngineUDK.ini"
$PackageRegex = "^\+ModEditPackages=${PackageName}`$"
(Get-Content $EngineFile) -replace "$PackageRegex", "" | Set-Content $EngineFile
}
function OmitCodePackages {
Param(
[string]$UdkPath,
[string[]]$PackageNames
)
foreach ($PackageName in $PackageNames) {
OmitCodePackage "$UdkPath" "$PackageName"
}
}
function CleanupSVN {
Param(
[string]$UdkPath
)
# Delete unversioned files created by previous runs (logs, generated INIs)
$ChangedFilesList = (svn status --no-ignore $UdkPath)
foreach ($ChangedFileEntry in $ChangedFilesList) {
$ChangedFileName = $ChangedFileEntry.Substring(1).Trim()
# Match unversioned (?) or ignored (I) files ending with: .ini, .log, .dmp
if ($ChangedFileEntry -match "^(\?|I).+\.(ini|log|dmp)$") {
Remove-Item -Path $ChangedFileName
}
}
# Delete CookedPC from previous runs
if ([System.IO.Directory]::Exists("$UdkPath\UDKGame\CookedPC\")) {
Write-Host "Deleting CookedPC..."
Remove-Item -Recurse -Force "$UdkPath\UDKGame\CookedPC\"
}
}
function MakeChangelog {
Param(
[string]$UdkPath,
[int]$OldRevision,
[int]$NewRevision
)
svn log -v -r ${OldRevision}:${NewRevision} "$UdkPath" | Out-File changelog.txt
}
function BuildScripts {
Param(
[string]$UdkExePath
)
& "$UdkExePath" make -full -buildmachine -stripsource -auto -unattended -nopause
}
function BuildShaders {
Param(
[string]$UdkPath
)
# Calculate paths
$MapsRootDir = "$UdkPath\UDKGame\Content\Maps"
$UdkExePath = "$UdkPath\Binaries\Win64\UDK.exe"
$RevisionFile = "$UdkPath\build\shaders_last_run.txt"
# Build MapsDirs
$MapsDirs = New-Object System.Collections.Generic.List[System.String]
$MapsDirs += "$MapsRootDir\RenX"
$MapsDirs += "$MapsRootDir\TiberianSun"
# Get last revision this script ran for
$LastRevision = 0
if ([System.IO.File]::Exists($RevisionFile)) {
$LastRevision = Get-Content $RevisionFile
}
$CurrentRevision = (GetSvnRevision $UdkPath)
# Get list of modified files
$Files = New-Object System.Collections.Generic.List[System.String]
foreach ($MapsDir in $MapsDirs) {
$ChangedFilesList = (svn diff "-r${LastRevision}:${CurrentRevision}" --summarize "$MapsDir")
foreach ($ChangedFileEntry in $ChangedFilesList) {
$ChangedFileName = $ChangedFileEntry.Substring(1).Trim()
if ([System.IO.File]::Exists($ChangedFileName)) {
$Files += $ChangedFileName
}
}
}
# Open each map to build shaders
foreach ($File in $Files) {
# Get package name from file
$PackageName = [System.IO.Path]::GetFileNameWithoutExtension($File)
# Open each level with exec script to build shaders
# Note: not using 'server' for now, since it seems not to respect -EXEC, possibly because it's a "rendering" option
Write-Host "Building shaders for: $PackageName"
$Cmd = "server ${PackageName}?mutator=RenX_ExampleMutators.ShaderHelper?bIsLanMatch=true -auto -unattended -nopause"
Start-Process -Wait -FilePath "$UdkExePath" -ArgumentList "$Cmd"
}
echo $CurrentRevision | Out-File $RevisionFile
}
function CookGame {
Param(
[string]$UdkPath,
[string]$CookParams
)
$UdkExePath = "$UdkPath\Binaries\Win64\UDK.com"
ForceDesktopSession
& "$UdkExePath" CookPackages $CookParams
$CookExitCode = $LASTEXITCODE
# Check if cook failed (exit code != 0)
if ($CookExitCode -ne 0) {
Write-Host "ERROR: Cook failed with exit code $CookExitCode"
exit 1
}
# Check if cook succeeded
if (Get-ChildItem "$UdkPath\UDKGame\CookedPC\Process_*") {
Write-Host "ERROR: Cook failed to complete (Process paths are still present). Exiting..."
exit 1
}
}
function StageCookedPC {
Param(
[string]$UdkPath,
[string]$BuildType
)
$CookedPCPath = "$UdkPath\UDKGame\CookedPC"
$TypedCookedPCPath = "$UdkPath\UDKGame\CookedPC_${BuildType}"
if ([System.IO.Directory]::Exists($TypedCookedPCPath)) {
Copy-Item -Path $TypedCookedPCPath -Destination $CookedPCPath -Recurse
}
}
function UnstageCookedPC {
Param(
[string]$UdkPath,
[string]$BuildType
)
$CookedPCPath = "$UdkPath\UDKGame\CookedPC"
$TypedCookedPCPath = "$UdkPath\UDKGame\CookedPC_${BuildType}"
if ([System.IO.Directory]::Exists($CookedPCPath)) {
if ([System.IO.Directory]::Exists($TypedCookedPCPath)) {
Remove-Item -Recurse -Force "$TypedCookedPCPath"
}
Rename-Item -Path $CookedPCPath -NewName $TypedCookedPCPath
}
}
function BuildUdk {
Param(
[string]$UdkPath,
[string]$BuildType
)
$UdkExePath = "$UdkPath\Binaries\Win64\UDK.com"
$BuildPath = "$UdkPath\build"
$TypeConfigData = (GetTypeConfig "$BuildPath" "$BuildType")
# Omit private code packages where necessary
OmitCodePackages "$UdkPath" $TypeConfigData.excluded_code
# Cleanup SVN data from previous runs
Write-Host "Cleaning up SVN from previous builds..."
CleanupSVN "$UdkPath"
# Move in working CookedPC
Write-Host "Staging CookedPC for $BuildType build..."
StageCookedPC "$UdkPath" "$BuildType"
# Compile scripts
Write-Host "Compiling scripts..."
BuildScripts "$UdkExePath"
# Build shaders for all changed levels
Write-Host "Building shaders..."
BuildShaders "$UdkPath"
Write-Host "Shader build finished"
# Cook packages
if ($TypeConfigData.cook) {
Write-Host "Cooking packages..."
$CookParams = (GetNodeConfig "$BuildPath" $Env:NODE_NAME).cook_params
CookGame "$UdkPath" "$CookParams"
}
}
function GetIncludedFiles {
Param(
[string]$UdkPath,
[string]$BuildType
)
$BuildPath = "$UdkPath\build"
# Read inclusion expressions
$IncludeExpressions = (Get-Content $BuildPath\expressions.include.txt)
if ([System.IO.File]::Exists("$BuildPath\expressions.include.$BuildType.txt")) {
$IncludeExpressions += (Get-Content "$BuildPath\expressions.include.$BuildType.txt")
}
# Read map list into inclusion expressions
$IncludeLevels = (Get-Content $BuildPath\levels.include.txt)
if ([System.IO.File]::Exists("$BuildPath\levels.include.$BuildType.txt")) {
$IncludeLevels += (Get-Content "$BuildPath\levels.include.$BuildType.txt")
}
foreach ($LevelName in $IncludeLevels) {
if ((![string]::IsNullOrWhiteSpace($LevelName)) -and # Ignore blank lines
(!$LevelName.StartsWith("#"))) { # Ignore comments/headers
$IncludeExpressions += "^PreviewVids\\$LevelName\..+$"
$IncludeExpressions += "^UDKGame\\Config\\$LevelName.ini$"
$IncludeExpressions += "^UDKGame\\Content\\Maps\\[A-Za-z]+\\$LevelName.udk$"
$IncludeExpressions += "^UDKGame\\Movies\\LoadingScreen_$LevelName.bik$"
}
}
# Checks if a file matches inclusion lists
function MatchesInclude {
param (
[string]$FilePath
)
# Iterate over all expressions and check if FilePath matches ANY of them
foreach ($Expression in $IncludeExpressions) {
if ((![string]::IsNullOrWhiteSpace($Expression)) -and # Ignore blank lines
(!$Expression.StartsWith("#")) -and # Ignore comments/headers
([System.IO.File]::Exists("$UdkPath\$FilePath")) -and # Ignore non-files
($FilePath -match $Expression)) { # Check if the path matches this expression
return $true
}
}
return $false
}
# Compile list of files to copy from SVN
Write-Host "Scanning SVN for necessary files..."
$Files = (Get-ChildItem -Recurse $UdkPath)
$IncludedFiles = New-Object System.Collections.Generic.List[System.String]
foreach ($File in $Files) {
# Check if file matches inclusion list
$Filename = $File.FullName.Substring($UdkPath.Length + 1)
if (MatchesInclude $Filename) {
$IncludedFiles += $Filename
}
}
echo $IncludedFiles | Out-File included_files.txt
return $IncludedFiles
}
function GenerateBuildData {
Param(
[string]$RootDir,
[string]$UdkPath,
[string]$BuildType,
[string]$PatchName,
[string]$VersionUrl
)
$CurrentRevision = (GetSvnRevision $UdkPath)
Write-Host "Generating new build"
$NewBuildPath = "$RootDir\Build${CurrentRevision}"
$IncludedFiles = (GetIncludedFiles "$UdkPath" "$BuildType")
$RetargetData = (GetTypeConfig "$UdkPath\build" "$BuildType").retargets
# Copy included files over to new build
Write-Host "Copying files from SVN..."
foreach ($Filename in $IncludedFiles) {
$SourceFilePath = "$UdkPath\$Filename"
$TargetFilePath = "$NewBuildPath\$Filename"
# If it's from Content, copy it to CookedPC instead (i.e: Maps); note: retarget strings moved to config.json
foreach ($Retarget in $RetargetData) {
if ($Filename -match $Retarget.source) {
$TargetFilename = $Filename -replace $Retarget.source, $RetargetData.target
$TargetFilePath = "$NewBuildPath\$TargetFilename"
break
}
}
# Create parent directories
New-Item -ItemType File -Force "$TargetFilePath" | Out-Null
Remove-Item $TargetFilePath
# Copy file
Write-Host "Copying $Filename..."
Copy-Item -Force "$SourceFilePath" "$TargetFilePath"
}
# Update version in INI
Write-Host "Updating version file..."
$VersionFile = "$NewBuildPath\UDKGame\Config\DefaultRenegadeX.ini"
$VersionStringRegex = '^GameVersion=".+"$'
$VersionNumberRegex = '^GameVersionNumber=.+$'
$VersionUrlRegex = '^MasterVersionURL=.+$'
(Get-Content $VersionFile) -replace $VersionStringRegex, "GameVersion=`"${PatchName}`"" | Set-Content $VersionFile
(Get-Content $VersionFile) -replace $VersionNumberRegex, "GameVersionNumber=$CurrentRevision" | Set-Content $VersionFile
(Get-Content $VersionFile) -replace $VersionUrlRegex, "MasterVersionURL=$VersionUrl" | Set-Content $VersionFile
}
function PrepRxPatch {
Param(
[string]$RxPatchPath,
[string]$RxPatchUrl
)
# Checkout RXPatch if it doesn't exist
if (![System.IO.Directory]::Exists("$RxPatchPath")) {
Write-Host "Checkout RXPatch..."
svn checkout $RxPatchUrl
}
# Enter RXPatch
svn update $RxPatchPath
}
function PullGameBuild {
Param(
[string]$BuildUrl,
[string]$BuildPath,
[string]$RxPatchExe
)
$PatchWorkPath = "${BuildPath}_patch_tmp"
# Prep paths
if (![System.IO.Directory]::Exists("$BuildPath")) {
New-Item -ItemType "Directory" $BuildPath | Out-Null
}
# Pull old build
& "$RxPatchExe" apply_web $BuildUrl $BuildPath $PatchWorkPath
# Cleanup PatchWorkPath
Remove-Item $PatchWorkPath -Recurse -Force
}
function GeneratePatchData {
Param(
[string]$PatchDataPath,
[string]$OldBuildPath,
[string]$NewBuildPath,
[string]$RxPatchExe
)
$ArgsFilePath = (New-TemporaryFile).FullName
# Write args file
$ArgsJson = [ordered]@{
OldPath = "$OldBuildPath"
NewPath = "$NewBuildPath"
PatchPath = "$PatchDataPath"
}
echo $ArgsJson | ConvertTo-Json | Out-File $ArgsFilePath
# Cleanup any old patch data
if ([System.IO.Directory]::Exists($PatchDataPath)) {
Remove-Item -Recurse -Force $PatchDataPath
}
# Build new patch data
& "$RxPatchExe" create $ArgsFilePath
# Cleanup args file
Remove-Item -Force $ArgsFilePath
}
function PublishPatchData {
Param(
[string]$SSHKey,
[string]$SSHUsername,
[string]$Destination,
[string]$PatchDataPath
)
$CurrentUsername = [Environment]::UserName
Write-Host "Setting permissions on SSH key..."
Icacls $SSHKey /c /t /Inheritance:d # Remove Inheritance
Icacls $SSHKey /c /t /Grant "${CurrentUsername}:F" # Set Ownership to Owner
Icacls $SSHKey /c /t /Remove "Authenticated Users" BUILTIN\Administrators BUILTIN Everyone System Users # Remove All Users, except for Owner
Write-Host "Pushing data to VCS..."
scp -i "$SSHKey" -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -r "$PatchDataPath" "$SSHUsername@$Destination"
# TODO: Replace this with a build step in pipeline
Write-Host "Triggering sync for mirrors..."
ssh -i "$SSHKey" -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL "$SSHUsername@vcs.glitchware.com" "/home/renx/patches/updaterepos.sh"
Write-Host "Patch data pushed to mirrors. Deleting local data..."
Remove-Item -Recurse -Force $PatchDataPath
}
function MakePatchData {
Param(
[string]$RootDir,
[string]$UdkPath,
[string]$BuildType,
[string]$PatchName,
[string]$SourceBranch,
[switch]$DeleteBuild
)
$BuildPath = "$UdkPath\build"
$RxPatchPath = "$RootDir\RXPatch"
$RxPatchUrl = "svn://svn.renegade-x.com/svn/main/RXPatch"
$RxPatchExe = "$RxPatchPath\RXPatch.exe"
$CurrentRevision = (GetSvnRevision $UdkPath)
$NewBuildPath = "$RootDir\Build${CurrentRevision}"
$TypeConfig = GetTypeConfig "$BuildPath" "$BuildType"
$PatchPrefix = $TypeConfig.prefix
$ProductKey = $TypeConfig.product
$PatchDataName = "${PatchPrefix}${CurrentRevision}"
$PatchDataPath = "$RootDir\$PatchDataName"
$MirrorUrl = (GetNodeConfig "$BuildPath" $Env:NODE_NAME).pull_mirror
# Get current version info
Write-Host "Fetching version info..."
$VersionUrl = "https://static.renegade-x.com/launcher_data/version/${SourceBranch}.json"
$VersionData = (GetVersionData "$VersionUrl")
$OldBuildNum = $VersionData.$ProductKey.version_number
$OldBuildPatchPath = $VersionData.$ProductKey.patch_path
$OldBuildUrl = "${MirrorUrl}${OldBuildPatchPath}"
$OldBuildPath = "$RootDir\Build${OldBuildNum}"
# Compose changelog
MakeChangelog "$UdkPath" "$OldBuildNum" "$CurrentRevision"
# Compose new build data
GenerateBuildData "$RootDir" "$UdkPath" "$BuildType" "$PatchName" "$VersionUrl" | Write-Host
UnstageCookedPC "$UdkPath" "$BuildType"
# Prepare RXPatch for patching
PrepRxPatch "$RxPatchPath" "$RxPatchUrl" | Write-Host
# Pull old patch data
Write-Host "Pulling previous build..."
PullGameBuild "$OldBuildUrl" "$OldBuildPath" "$RxPatchExe" | Write-Host
# Generate patch data
Write-Host "Generating patch data"
GeneratePatchData "$PatchDataPath" "$OldBuildPath" "$NewBuildPath" "$RxPatchExe" | Write-Host
# Cleanup build files
Remove-Item -Recurse -Force $OldBuildPath
if ($DeleteBuild) {
Remove-Item -Recurse -Force $NewBuildPath
}
# Verify instructions.json is generated, and report failure if it's not present
if (![System.IO.File]::Exists("$PatchDataPath\instructions.json")) {
Write-Host "Patch data failed to generate"
Exit 1
}
Write-Host "Patch data generated successfully"
return $PatchDataName
}