Skip to content

ci: Implement Replay Checker #1366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 94 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
7c2b950
feat(ci): validate replays for mismatches
Skaronator Jul 6, 2025
ad823d1
fix(ci): Move replay files to correct location, adjust exe call
Skaronator Jul 7, 2025
21ea5c1
fix(ci): Make sure to pass -replay to the generalszh.exe
Skaronator Jul 8, 2025
0d1a5e3
Make game compile in VS2019
helmutbuhler Jul 13, 2025
32354e0
Simplify typename hack
helmutbuhler Jul 14, 2025
86668c8
Disable Generalsbuild, reenable VC6
helmutbuhler Jul 23, 2025
51350c3
disable vs22 builds
helmutbuhler Jul 23, 2025
6b3322c
Fix variable in CI
helmutbuhler Jul 23, 2025
48a6696
Add gamedata download to CI
helmutbuhler Jul 23, 2025
2a7b48c
Fix syntax
helmutbuhler Jul 23, 2025
458e0e1
add hack
helmutbuhler Jul 23, 2025
f94ba14
more hacks
helmutbuhler Jul 23, 2025
6f99ce5
fix syntax
helmutbuhler Jul 23, 2025
5f7df8f
Add timeout and log output
helmutbuhler Jul 23, 2025
1ca180d
change name and improve log output
helmutbuhler Jul 23, 2025
b3e8593
Fix +t+e
helmutbuhler Jul 23, 2025
0b90964
upload log
helmutbuhler Jul 23, 2025
a05d139
always List All Files
helmutbuhler Jul 24, 2025
d2616eb
move gamedata subfolders
helmutbuhler Jul 24, 2025
f98107b
make debug build with opt
helmutbuhler Jul 24, 2025
da0ec33
fix order
helmutbuhler Jul 24, 2025
1588493
Update Run Replay Compatibility Tests
helmutbuhler Jul 24, 2025
8b44241
add stdout.log upload
helmutbuhler Jul 24, 2025
b8bcfcb
Merge remote-tracking branch 'remotes/hb/VS2019_compat' into ci-replays
helmutbuhler Jul 24, 2025
6ec3210
Disable InsertCDMessage messagebox in headless mode
helmutbuhler Jul 24, 2025
e78a948
Update gamedata to include generals data
helmutbuhler Jul 24, 2025
cea43aa
Use Current User for registry
helmutbuhler Jul 24, 2025
51d3d55
add registry logging
helmutbuhler Jul 24, 2025
b25690e
update slash in path
helmutbuhler Jul 24, 2025
4b34215
fix replay moving and change exitcode detection
helmutbuhler Jul 24, 2025
355c9ed
dummy
helmutbuhler Jul 24, 2025
c39b8bb
fix replay path
helmutbuhler Jul 24, 2025
53ab2a5
dummy
helmutbuhler Jul 24, 2025
124be98
add vc6-releaselog config
helmutbuhler Jul 24, 2025
6a83c69
update buildPresets
helmutbuhler Jul 24, 2025
d9eeb11
fix gamedata copy
helmutbuhler Jul 24, 2025
6d267dc
Increase timeout
helmutbuhler Jul 24, 2025
57b5d6c
Restructure replays and add maps
helmutbuhler Jul 25, 2025
f787c8c
Handle maps in CI
helmutbuhler Jul 25, 2025
bdaaad3
Fix move and comments
helmutbuhler Jul 25, 2025
0b09452
comments
helmutbuhler Jul 25, 2025
1ed3877
fix warning and try out jobs
helmutbuhler Jul 25, 2025
dc33dc6
Rename ReplayCheck folder to Test
helmutbuhler Jul 25, 2025
74454c4
check crashing mismatch
helmutbuhler Jul 25, 2025
93af880
Delete debug code
helmutbuhler Jul 25, 2025
fdd4fe0
Revert "check crashing mismatch"
helmutbuhler Jul 25, 2025
dc82800
Remove Hello CI
helmutbuhler Jul 25, 2025
f048dc7
Use 2 jobs
helmutbuhler Jul 25, 2025
d72f44c
Rename Golden Replay #1.rep
helmutbuhler Jul 25, 2025
add354e
Remove hacks
helmutbuhler Jul 25, 2025
9550d2f
3 jobs
helmutbuhler Jul 25, 2025
85f4929
4 jobs
helmutbuhler Jul 25, 2025
b4ae422
Rename !Golden Replay #1.rep
helmutbuhler Jul 25, 2025
2cf638e
comment and 5 jobs
helmutbuhler Jul 25, 2025
17ee34a
set jobs and timeout
helmutbuhler Jul 26, 2025
a74dcce
put check replays ci stuff into own file
helmutbuhler Jul 26, 2025
1027157
fix name
helmutbuhler Jul 26, 2025
619c80f
adjust timeout and name in build-toolchain
helmutbuhler Jul 26, 2025
0b51d18
enable other buildconfigs again
helmutbuhler Jul 26, 2025
f25646d
enable old mismatch for testing
helmutbuhler Jul 26, 2025
3edba8f
fix indent
helmutbuhler Jul 26, 2025
0a253cd
build-generalsmd is split into two jobs for vc6 and win32
helmutbuhler Jul 26, 2025
1a08e85
Revert "enable old mismatch for testing"
helmutbuhler Jul 26, 2025
8e92e58
add comment
helmutbuhler Jul 26, 2025
73f4cf3
Add headless check in Generals
helmutbuhler Jul 26, 2025
9095c5f
remove old code
helmutbuhler Jul 26, 2025
0ef4b05
Remove temp url
helmutbuhler Jul 26, 2025
658a71f
add readme
helmutbuhler Jul 26, 2025
7846e4d
make sure no crash messagebox appears in headless mode
helmutbuhler Jul 26, 2025
41e41ab
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Jul 26, 2025
481f023
add errormessage if envs are missing
helmutbuhler Jul 26, 2025
08bb4b9
Refactor ignoringAsserts()
helmutbuhler Jul 28, 2025
403c8c4
Use %USERPROFILE% in readme
helmutbuhler Jul 28, 2025
605b936
Mention Generals 1.08 and Generals Zero Hour 1.04 in check-replays.yml
helmutbuhler Jul 28, 2025
4c3591d
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Jul 28, 2025
61338bb
Replay Check GeneralsMD
helmutbuhler Jul 29, 2025
90ab1ad
Merge remote-tracking branch 'remotes/sh/main' into ci-replays
helmutbuhler Jul 30, 2025
627a2d7
chore(submodule): Add 'GeneralsReplays' submodule
xezon Aug 2, 2025
e412623
Delete replays and maps in Code git
helmutbuhler Aug 2, 2025
2c15164
Update S3 filenames
helmutbuhler Aug 2, 2025
ca5c6b9
add filename logging
helmutbuhler Aug 2, 2025
0bf0abc
Adjust path for replays
helmutbuhler Aug 2, 2025
be6b9f1
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Aug 2, 2025
1c42f49
Update comment
helmutbuhler Aug 2, 2025
a6ca81c
Remove debug logging
helmutbuhler Aug 2, 2025
d42d343
Move Test readme
helmutbuhler Aug 2, 2025
c39f6bf
Update testing readme
helmutbuhler Aug 2, 2025
f0ff46c
Update comment
helmutbuhler Aug 2, 2025
264221c
Use variables for C:\GameData and subfolders
helmutbuhler Aug 3, 2025
c378d4b
fix spelling
helmutbuhler Aug 3, 2025
f927703
Change logging
helmutbuhler Aug 3, 2025
b411250
rename job input gamedata to userdata
helmutbuhler Aug 3, 2025
68e1f8e
Try out envs in job
helmutbuhler Aug 3, 2025
a2cb477
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Aug 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-toolchain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ on:

jobs:
build:
name: Preset ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }}
name: ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }}
runs-on: windows-2022
timeout-minutes: 40
timeout-minutes: 20
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down
240 changes: 240 additions & 0 deletions .github/workflows/check-replays.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
name: check-replays

permissions:
contents: read
pull-requests: write

on:
workflow_call:
inputs:
game:
required: true
type: string
description: "Game to check (only GeneralsMD for now)"
userdata:
required: true
type: string
description: "Path to folder with replays and maps"
preset:
required: true
type: string
description: "CMake preset"

jobs:
build:
name: ${{ inputs.preset }}
runs-on: windows-latest
timeout-minutes: 15
env:
GAME_PATH: C:\GameData
GENERALS_PATH: C:\GameData\Generals
GENERALSMD_PATH: C:\GameData\GeneralsMD
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
submodules: true

- name: Download Game Artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.game }}-${{ inputs.preset }}
path: build

- name: Cache Game Data
id: cache-gamedata
uses: actions/cache@v4
with:
path: ${{ env.GAME_PATH }}
key: gamedata-permanent-cache-v3

- name: Download Game Data from Cloudflare R2
if: ${{ steps.cache-gamedata.outputs.cache-hit != 'true' }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }}
EXPECTED_HASH_GENERALS: "37A351AA430199D1F05DEB9E404857DCE7B461A6AC272C5D4A0B5652CDB06372"
EXPECTED_HASH_GENERALSMD: "6837FE1E3009A4C239406C39B1598216C0943EE8ED46BB10626767029AC05E21"
shell: pwsh
run: |
# Download trimmed gamedata of both Generals 1.08 and Generals Zero Hour 1.04.
# This data cannot be used for playing because it's
# missing textures, audio and gui files. But it's enough for replay checking.
# It's also encrypted because it's not allowed to distribute these files.

if (-not $env:AWS_ACCESS_KEY_ID -or -not $env:AWS_SECRET_ACCESS_KEY -or -not $env:AWS_ENDPOINT_URL) {
$ok1 = [bool]$env:AWS_ACCESS_KEY_ID
$ok2 = [bool]$env:AWS_SECRET_ACCESS_KEY
$ok3 = [bool]$env:AWS_ENDPOINT_URL
Write-Host "One or more required secrets are not set or are empty. R2_ACCESS_KEY_ID: $ok1, R2_SECRET_ACCESS_KEY: $ok2, R2_ENDPOINT_URL: $ok3"
exit 1
}

# Download Generals Game Files
# The archive contains these files:
# BINKW32.DLL
# English.big
# INI.big
# Maps.big
# mss32.dll
# W3D.big
# Data\Scripts\MultiplayerScripts.scb
# Data\Scripts\SkirmishScripts.scb

Write-Host "Downloading Game Data for Generals" -ForegroundColor Cyan
aws s3 cp s3://github-ci/generals108_gamedata_trimmed.7z generals108_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL

Write-Host "Verifying File Integrity" -ForegroundColor Cyan
$fileHash = (Get-FileHash -Path generals108_gamedata_trimmed.7z -Algorithm SHA256).Hash
Write-Host "Downloaded file SHA256: $fileHash"
Write-Host "Expected file SHA256: $env:EXPECTED_HASH_GENERALS"
if ($fileHash -ne $env:EXPECTED_HASH_GENERALS) {
Write-Error "Hash verification failed! File may be corrupted or tampered with."
exit 1
}

Write-Host "Extracting Archive" -ForegroundColor Cyan
& 7z x generals108_gamedata_trimmed.7z -o$env:GENERALS_PATH
Remove-Item generals108_gamedata_trimmed.7z -Verbose

# Download GeneralsMD (ZH) Game Files
# The archive contains these files:
# BINKW32.DLL
# INIZH.big
# MapsZH.big
# mss32.dll
# W3DZH.big
# Data\Scripts\MultiplayerScripts.scb
# Data\Scripts\Scripts.ini
# Data\Scripts\SkirmishScripts.scb

Write-Host "Downloading Game Data for GeneralsMD" -ForegroundColor Cyan
aws s3 cp s3://github-ci/zerohour104_gamedata_trimmed.7z zerohour104_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL

Write-Host "Verifying File Integrity" -ForegroundColor Cyan
$fileHash = (Get-FileHash -Path zerohour104_gamedata_trimmed.7z -Algorithm SHA256).Hash
Write-Host "Downloaded file SHA256: $fileHash"
Write-Host "Expected file SHA256: $env:EXPECTED_HASH_GENERALSMD"
if ($fileHash -ne $env:EXPECTED_HASH_GENERALSMD) {
Write-Error "Hash verification failed! File may be corrupted or tampered with."
exit 1
}

Write-Host "Extracting Archive" -ForegroundColor Cyan
& 7z x zerohour104_gamedata_trimmed.7z -o$env:GENERALSMD_PATH
Remove-Item zerohour104_gamedata_trimmed.7z -Verbose

- name: Set Up Game Data
shell: pwsh
run: |
$source = "$env:GAME_PATH\${{ inputs.game }}"
$destination = "build"
Copy-Item -Path $source\* -Destination $destination -Recurse -Force

- name: Set Generals InstallPath in Registry
shell: pwsh
run: |
# Zero Hour loads some Generals files and needs this registry key to find the
# Generals data files.

$regPath = "HKCU:\SOFTWARE\Electronic Arts\EA Games\Generals"
$installPath = "$env:GENERALS_PATH\"

# Ensure the key exists
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}

# Set the InstallPath value
Set-ItemProperty -Path $regPath -Name InstallPath -Value $installPath -Type String
Write-Host "Registry key set: $regPath -> InstallPath = $installPath"

- name: Move Replays and Maps to User Dir
shell: pwsh
run: |
# These files are expected in the user dir, so we move them here.

$source = "${{ inputs.userdata }}\Replays"
$destination = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Replays"
Write-Host "Move replays to $destination"
New-Item -ItemType Directory -Path $destination -Force | Out-Null
Move-Item -Path "$source\*" -Destination $destination -Force

$source = "${{ inputs.userdata }}\Maps"
$destination = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Maps"
Write-Host "Move maps to $destination"
New-Item -ItemType Directory -Path $destination -Force | Out-Null
Move-Item -Path "$source\*" -Destination $destination -Force

- name: Run Replay Compatibility Tests
shell: pwsh
run: |
$exePath = "build/generalszh.exe"
$arguments = "-jobs 4 -headless -replay *.rep"
$timeoutSeconds = 10*60
$stdoutPath = "stdout.log"
$stderrPath = "stderr.log"

if (-not (Test-Path $exePath)) {
Write-Host "ERROR: Executable not found at $exePath"
exit 1
}

# Note that the game is a gui application. That means we need to redirect console output to a file
# in order to retrieve it.
# Clean previous logs
Remove-Item $stdoutPath, $stderrPath -ErrorAction SilentlyContinue

# Start the process
Write-Host "Run $exePath $arguments"
$process = Start-Process -FilePath $exePath `
-ArgumentList $arguments `
-RedirectStandardOutput $stdoutPath `
-RedirectStandardError $stderrPath `
-PassThru

# Wait with timeout
$exited = $process.WaitForExit($timeoutSeconds * 1000)

if (-not $exited) {
Write-Host "ERROR: Process still running after $timeoutSeconds seconds. Killing process..."
Stop-Process -Id $process.Id -Force
}

# Read output
Write-Host "=== STDOUT ==="
Get-Content $stdoutPath

if ((Test-Path $stderrPath) -and (Get-Item $stderrPath).Length -gt 0) {
Write-Host "`n=== STDERR ==="
Get-Content $stderrPath
}

if (-not $exited) {
exit 1
}

# Check exit code
$exitCode = $process.ExitCode

# The above doesn't work on all Windows versions. If not, try this: (see https://stackoverflow.com/a/16018287)
#$process.HasExited | Out-Null # Needs to be called for the command below to work correctly
#$exitCode = $process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
#Write-Host "exit code $exitCode"

if ($exitCode -ne 0) {
Write-Host "ERROR: Process failed with exit code $exitCode"
exit $exitCode
}

Write-Host "Success!"

- name: Upload Debug Log
if: always()
uses: actions/upload-artifact@v4
with:
name: Replay-Debug-Log-${{ inputs.preset }}
path: build/DebugLogFile*.txt
retention-days: 30
if-no-files-found: ignore
43 changes: 40 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ jobs:
generalsmd:
- 'GeneralsMD/**'
shared:
- '.github/workflows/build-toolchain.yml'
- '.github/workflows/ci.yml'
- '.github/workflows/**'
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'cmake/**'
Expand Down Expand Up @@ -100,7 +99,9 @@ jobs:
extras: ${{ matrix.extras }}
secrets: inherit

build-generalsmd:
# Note build-generalsmd is split into two jobs for vc6 and win32 because replaycheck-generalsmd
# only requires the vc6 build and compiling vc6 is much faster than win32
build-generalsmd-vc6:
name: Build GeneralsMD${{ matrix.preset && '' }}
needs: detect-changes
if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }}
Expand All @@ -116,6 +117,25 @@ jobs:
- preset: "vc6-debug"
tools: true
extras: true
- preset: "vc6-releaselog"
tools: true
extras: true
fail-fast: false
uses: ./.github/workflows/build-toolchain.yml
with:
game: "GeneralsMD"
preset: ${{ matrix.preset }}
tools: ${{ matrix.tools }}
extras: ${{ matrix.extras }}
secrets: inherit

build-generalsmd-win32:
name: Build GeneralsMD${{ matrix.preset && '' }}
needs: detect-changes
if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }}
strategy:
matrix:
include:
- preset: "win32"
tools: true
extras: true
Expand Down Expand Up @@ -143,3 +163,20 @@ jobs:
tools: ${{ matrix.tools }}
extras: ${{ matrix.extras }}
secrets: inherit

replaycheck-generalsmd:
name: Replay Check GeneralsMD${{ matrix.preset && '' }}
needs: build-generalsmd-vc6
if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }}
strategy:
matrix:
include:
- preset: "vc6+t+e"
- preset: "vc6-releaselog+t+e" # optimized build with logging and crashing enabled should be compatible, so we test that here.
fail-fast: false
uses: ./.github/workflows/check-replays.yml
with:
game: "GeneralsMD"
userdata: "GeneralsReplays/GeneralsZH/1.04"
preset: ${{ matrix.preset }}
secrets: inherit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
!.gitignore
!.gitattributes
!.github
!.gitmodules

*.user
*.ncb
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "GeneralsReplays"]
path = GeneralsReplays
url = https://github.com/TheSuperHackers/GeneralsReplays
28 changes: 28 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
"RTS_BUILD_OPTION_DEBUG": "ON"
}
},
{
"name": "vc6-releaselog",
"displayName": "Windows 32bit VC6 Release Logging",
"inherits": "vc6",
"cacheVariables": {
"RTS_DEBUG_LOGGING": "ON",
"RTS_DEBUG_CRASHING": "ON"
}
},
{
"name": "default",
"displayName": "Default Config (don't use directly!)",
Expand Down Expand Up @@ -163,6 +172,12 @@
"displayName": "Build Windows 32bit VC6 Debug",
"description": "Build Windows 32bit VC6 Debug"
},
{
"name": "vc6-releaselog",
"configurePreset": "vc6-releaselog",
"displayName": "Build Windows 32bit VC6 Release Logging",
"description": "Build Windows 32bit VC6 Release Logging"
},
{
"name": "win32",
"configurePreset": "win32",
Expand Down Expand Up @@ -253,6 +268,19 @@
}
]
},
{
"name": "vc6-releaselog",
"steps": [
{
"type": "configure",
"name": "vc6-releaselog"
},
{
"type": "build",
"name": "vc6-releaselog"
}
]
},
{
"name": "win32",
"steps": [
Expand Down
1 change: 1 addition & 0 deletions GeneralsReplays
Submodule GeneralsReplays added at af0c61
14 changes: 14 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Test Replays

The GeneralsReplays folder contains replays and the required maps that are tested in CI to ensure that the game is retail compatible.

You can also test with these replays locally:
- Copy the replays into a subfolder in your `%USERPROFILE%/Documents/Command and Conquer Generals Zero Hour Data/Replays` folder.
- Copy the maps into `%USERPROFILE%/Documents/Command and Conquer Generals Zero Hour Data/Maps`
- Start the test with this: (copy into a .bat file next to your executable)
```
START /B /W generalszh.exe -jobs 4 -headless -replay subfolder/*.rep > replay_check.log
echo %errorlevel%
PAUSE
```
It will run the game in the background and check that each replay is compatible. You need to use a VC6 build with optimizations and RTS_BUILD_OPTION_DEBUG = OFF, otherwise the game won't be compatible.
Loading