@@ -2,7 +2,6 @@ name: Build Toolchain
22
33permissions :
44 contents : read
5- pull-requests : write
65
76on :
87 workflow_call :
2524 default : false
2625 type : boolean
2726 description : " Build extras"
27+ replay :
28+ required : false
29+ default : false
30+ type : boolean
31+ description : " Run replay compatibility check after build"
2832
2933jobs :
3034 build :
4044 steps :
4145 - name : Checkout Code
4246 uses : actions/checkout@v4
47+ with :
48+ submodules : ${{ inputs.replay }}
4349
4450 - name : Cache VC6 Installation
4551 if : startsWith(inputs.preset, 'vc6')
5460 uses : actions/cache@v4
5561 with :
5662 path : build\${{ inputs.preset }}\_deps
57- key : cmake-deps-${{ inputs.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }}
63+ key : cmake-deps-${{ inputs.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','Dependencies/ **/CMakeLists.txt', ' CMakeLists.txt') }}
5864
5965 - name : Download VC6 Portable from itsmattkc repo
6066 if : ${{ startsWith(inputs.preset, 'vc6') && steps.cache-vc6.outputs.cache-hit != 'true' }}
@@ -106,7 +112,7 @@ jobs:
106112 arch : x86
107113
108114 - name : Compute vcpkg cache key parts
109- if : startsWith (inputs.preset, 'win32 ')
115+ if : contains (inputs.preset, 'vcpkg ')
110116 id : vcpkg_key
111117 shell : pwsh
112118 run : |
@@ -128,7 +134,7 @@ jobs:
128134 Write-Host "vcpkg cache key parts: baseline=$baseline, msvc=$msvcMajorMinor, triplet=$triplet"
129135
130136 - name : Restore vcpkg binary cache
131- if : startsWith (inputs.preset, 'win32 ')
137+ if : contains (inputs.preset, 'vcpkg ')
132138 id : vcpkg_cache
133139 uses : actions/cache/restore@v4
134140 with :
@@ -139,13 +145,14 @@ jobs:
139145 vcpkg-bincache-v2-${{ runner.os }}-
140146
141147 - name : Setup vcpkg
148+ if : contains(inputs.preset, 'vcpkg')
142149 uses : lukka/run-vcpkg@v11
143150 with :
144151 runVcpkgInstall : false
145152 doNotCache : true
146153
147154 - name : Configure vcpkg to use cached directory
148- if : startsWith (inputs.preset, 'win32 ')
155+ if : contains (inputs.preset, 'vcpkg ')
149156 shell : pwsh
150157 run : |
151158 $cacheDir = "${{ github.workspace }}\vcpkg-bincache"
@@ -182,7 +189,7 @@ jobs:
182189
183190 - name : Save vcpkg binary cache
184191 # Only one job should save to avoid "Unable to reserve cache" conflicts.
185- if : ${{ startsWith (inputs.preset, 'win32 ') && steps.vcpkg_cache.outputs.cache-hit != 'true' && inputs.game == 'Generals' && inputs.preset == 'win32-vcpkg-debug' }}
192+ if : ${{ contains (inputs.preset, 'vcpkg ') && steps.vcpkg_cache.outputs.cache-hit != 'true' && inputs.game == 'Generals' && inputs.preset == 'win32-vcpkg-debug' }}
186193 uses : actions/cache/save@v4
187194 with :
188195 path : ${{ github.workspace }}\vcpkg-bincache
@@ -212,3 +219,118 @@ jobs:
212219 path : build\${{ inputs.preset }}\${{ inputs.game }}\artifacts
213220 retention-days : 30
214221 if-no-files-found : error
222+
223+ # Replay Check Steps (only run when replay: true)
224+ - name : Cache Game Data
225+ if : inputs.replay
226+ id : cache-gamedata
227+ uses : actions/cache@v4
228+ with :
229+ path : C:\GameData
230+ key : gamedata-permanent-cache-v4
231+
232+ - name : Download Game Data from Cloudflare R2
233+ if : ${{ inputs.replay && steps.cache-gamedata.outputs.cache-hit != 'true' }}
234+ env :
235+ AWS_ACCESS_KEY_ID : ${{ secrets.R2_ACCESS_KEY_ID }}
236+ AWS_SECRET_ACCESS_KEY : ${{ secrets.R2_SECRET_ACCESS_KEY }}
237+ AWS_ENDPOINT_URL : ${{ secrets.R2_ENDPOINT_URL }}
238+ EXPECTED_HASH_GENERALS : " 37A351AA430199D1F05DEB9E404857DCE7B461A6AC272C5D4A0B5652CDB06372"
239+ EXPECTED_HASH_GENERALSMD : " 6837FE1E3009A4C239406C39B1598216C0943EE8ED46BB10626767029AC05E21"
240+ shell : pwsh
241+ run : |
242+ if (-not $env:AWS_ACCESS_KEY_ID -or -not $env:AWS_SECRET_ACCESS_KEY -or -not $env:AWS_ENDPOINT_URL) {
243+ Write-Host "One or more required secrets are not set or are empty."
244+ exit 1
245+ }
246+
247+ Write-Host "Downloading Game Data for Generals" -ForegroundColor Cyan
248+ aws s3 cp s3://github-ci/generals108_gamedata_trimmed.7z generals108_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL
249+ $fileHash = (Get-FileHash -Path generals108_gamedata_trimmed.7z -Algorithm SHA256).Hash
250+ if ($fileHash -ne $env:EXPECTED_HASH_GENERALS) {
251+ Write-Error "Hash verification failed for Generals!"
252+ exit 1
253+ }
254+ & 7z x generals108_gamedata_trimmed.7z -o"C:\GameData\Generals"
255+ Remove-Item generals108_gamedata_trimmed.7z
256+
257+ Write-Host "Downloading Game Data for GeneralsMD" -ForegroundColor Cyan
258+ aws s3 cp s3://github-ci/zerohour104_gamedata_trimmed.7z zerohour104_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL
259+ $fileHash = (Get-FileHash -Path zerohour104_gamedata_trimmed.7z -Algorithm SHA256).Hash
260+ if ($fileHash -ne $env:EXPECTED_HASH_GENERALSMD) {
261+ Write-Error "Hash verification failed for GeneralsMD!"
262+ exit 1
263+ }
264+ & 7z x zerohour104_gamedata_trimmed.7z -o"C:\GameData\GeneralsMD"
265+ Remove-Item zerohour104_gamedata_trimmed.7z
266+
267+ - name : Set Up Replay Check
268+ if : inputs.replay
269+ shell : pwsh
270+ run : |
271+ # Copy game data to build directory
272+ Copy-Item -Path "C:\GameData\${{ inputs.game }}\*" -Destination "build\${{ inputs.preset }}\${{ inputs.game }}\artifacts" -Recurse -Force
273+
274+ # Set Generals InstallPath in Registry (needed by Zero Hour)
275+ $regPath = "HKCU:\SOFTWARE\Electronic Arts\EA Games\Generals"
276+ if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
277+ Set-ItemProperty -Path $regPath -Name InstallPath -Value "C:\GameData\Generals\" -Type String
278+
279+ # Move Replays and Maps to User Dir
280+ $replaySource = "GeneralsReplays/GeneralsZH/1.04/Replays"
281+ $replayDest = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Replays"
282+ New-Item -ItemType Directory -Path $replayDest -Force | Out-Null
283+ Copy-Item -Path "$replaySource\*" -Destination $replayDest -Recurse -Force
284+
285+ $mapSource = "GeneralsReplays/GeneralsZH/1.04/Maps"
286+ $mapDest = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Maps"
287+ New-Item -ItemType Directory -Path $mapDest -Force | Out-Null
288+ Copy-Item -Path "$mapSource\*" -Destination $mapDest -Recurse -Force
289+
290+ - name : Run Replay Compatibility Tests
291+ if : inputs.replay
292+ shell : pwsh
293+ run : |
294+ $exePath = "build\${{ inputs.preset }}\${{ inputs.game }}\artifacts\generalszh.exe"
295+ $arguments = "-jobs 4 -headless -replay *.rep"
296+ $timeoutSeconds = 600
297+ $stdoutPath = "stdout.log"
298+ $stderrPath = "stderr.log"
299+
300+ if (-not (Test-Path $exePath)) {
301+ Write-Host "ERROR: Executable not found at $exePath"
302+ exit 1
303+ }
304+
305+ Remove-Item $stdoutPath, $stderrPath -ErrorAction SilentlyContinue
306+ Write-Host "Run $exePath $arguments"
307+ $process = Start-Process -FilePath $exePath -ArgumentList $arguments -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -PassThru
308+ $exited = $process.WaitForExit($timeoutSeconds * 1000)
309+
310+ if (-not $exited) {
311+ Write-Host "ERROR: Process timed out after $timeoutSeconds seconds"
312+ Stop-Process -Id $process.Id -Force
313+ }
314+
315+ Write-Host "=== STDOUT ==="
316+ Get-Content $stdoutPath
317+ if ((Test-Path $stderrPath) -and (Get-Item $stderrPath).Length -gt 0) {
318+ Write-Host "`n=== STDERR ==="
319+ Get-Content $stderrPath
320+ }
321+
322+ if (-not $exited) { exit 1 }
323+ if ($process.ExitCode -ne 0) {
324+ Write-Host "ERROR: Process failed with exit code $($process.ExitCode)"
325+ exit $process.ExitCode
326+ }
327+ Write-Host "Success!"
328+
329+ - name : Upload Replay Debug Log
330+ if : ${{ inputs.replay && always() }}
331+ uses : actions/upload-artifact@v4
332+ with :
333+ name : Replay-Debug-Log-${{ inputs.preset }}
334+ path : build\${{ inputs.preset }}\${{ inputs.game }}\artifacts\DebugLogFile*.txt
335+ retention-days : 30
336+ if-no-files-found : ignore
0 commit comments