feat(update-checker): add in-game update notification via GitHub Releases API#98
Merged
Conversation
…ases API Implements a background update checker for both GeneralsX and GeneralsXZH using libcurl + SDL thread. On tagged release builds, checks the latest release tag from the GitHub API and shows a dynamic button at bottom-right of the main menu if a newer version is available. - Add UpdateChecker.h/.cpp to both Generals/ and GeneralsMD/ with libcurl-based async background check (SDL_CreateThread + SDL_AtomicInt) - Guard feature behind SAGE_UPDATE_CHECK cmake flag (auto-ON with SAGE_USE_SDL3) - Add curl.cmake and libcurl dependency (vcpkg, sectransp SSL on macOS) - Add m_checkForUpdates to GlobalData for both games - Replace hardcoded ButtonGetUpdate (WND position 622,32) with a dynamically created push button via gogoGadgetPushButton at bottom-right using real screen coordinates (TheDisplay->getWidth/Height()) - Apply Arial 10pt font via winFindFont to avoid oversized WND default font - Show 'Update available: vX.X' text + tooltip 'click to download' on hover - SDL_OpenURL to https://github.com/fbraz3/GeneralsX/releases on click - Support GENERALS_FORCE_UPDATE_CHECK env var for testing in non-debug builds - Update CMakeLists for both GameEngine and GameEngineDevice targets - Update flatpak YMLs (com.fbraz3.GeneralsX, com.fbraz3.GeneralsXZH) with curl - Rename CLAUDE.md to AGENTS.md for AI agent compatibility Closes #51
…-tag CI job - Move UpdateChecker.h/.cpp from Generals/ and GeneralsMD/ to Core/GameEngine/ as shared INTERFACE source, eliminating duplication between the two games - Fix git_watcher.cmake: use 'describe --exact-match --tags' instead of 'describe --tags' so GitTag is only set on exact tagged commits, never on dev builds that have a release tag somewhere in history - Add create-tag CI job to release.yml that runs BEFORE build jobs, ensuring the tag exists in the repository when binaries are compiled (GitTag is set) - Remove CURL link entries from both GameEngineDevice CMakeLists (now propagated transitively via corei_gameengine_public INTERFACE)
Moving the dry_run guard from the inner step to the job level so the entire job is skipped (status: skipped) rather than running and doing nothing. Build jobs treat a skipped dependency as success and proceed normally. No tag should ever be created or pushed in a dry run.
…g is set GENERALS_FORCE_UPDATE_CHECK=1 was bypassing the guard in start() but not the second GitTag[0] == '\0' check inside threadFunc, causing the thread to return early and never set s_hasUpdate. In force mode, treat any non-empty tag from the GitHub API response as 'update available'.
Add cleanup-tag-on-failure job that runs when create-tag succeeded but any downstream job (builds or prepare-release) failed. This prevents the 'tag already exists' error on retry with the same version. Condition: failure() && needs.create-tag.result == 'success' - Skipped on dry_run (create-tag is skipped, result != 'success') - Skipped when everything succeeds (failure() is false) - Runs only when tag was pushed but builds/release preparation failed
SDL3/SDL.h was included directly in UpdateChecker.h for the private static member types (SDL_Thread*, SDL_AtomicInt). This caused build failures on Linux CI and macOS where MainMenu.cpp (part of g_gameengine) includes UpdateChecker.h but g_gameengine does not have SDL3 on its direct include path -- SDL3 only enters via g_gameenginedevice. Fix: convert class static members and threadFunc to file-scoped statics in UpdateChecker.cpp. The header now exposes only the public interface (start, poll, getReleasesUrl) with no SDL3 types, following pimpl-lite pattern for static-only utility classes.
…ATE_CHECK builds MainMenu.cpp includes <SDL3/SDL.h> directly to call SDL_OpenURL. On macOS the build passed because vcpkg adds SDL3 headers globally as an isystem path. On Linux CI (linux64-deploy), SDL3 comes from FetchContent and its include directories are only propagated to targets that explicitly depend on SDL3::SDL3 -- g_gameengine and z_gameengine did not. Fix: add SDL3::SDL3 as an INTERFACE dependency of corei_gameengine_public inside the SAGE_UPDATE_CHECK block. Since SAGE_UPDATE_CHECK is only ON when SAGE_USE_SDL3 is also ON, SDL3::SDL3 always exists at this point. The include dirs propagate transitively to all gameengine consumers.
There was a problem hiding this comment.
Pull request overview
Adds an in-game update notification feature for the SDL3 cross-platform builds by querying GitHub Releases in the background, and wires it into both Generals and Zero Hour main menus. This also adjusts build/release plumbing so tagged builds embed GitTag consistently for gating the feature.
Changes:
- Introduce
UpdateChecker(Core) using SDL thread + libcurl againstreleases/latest, with Options.ini opt-out and main menu UI hook. - Add/propagate build flags and dependencies (
SAGE_UPDATE_CHECK, curl via vcpkg/CMake, flatpak enablement, presets). - Update release workflow/tag embedding behavior (
git describe --exact-match) and rename AI agent instructions file.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
Core/GameEngine/Include/Common/UpdateChecker.h |
Declares the update-checker interface (guarded by SAGE_UPDATE_CHECK). |
Core/GameEngine/Source/Common/UpdateChecker.cpp |
Implements background GitHub release check via libcurl + SDL thread and polling API. |
Core/GameEngine/CMakeLists.txt |
Propagates UpdateChecker source and links curl/SDL3 when enabled. |
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp |
Starts/polls update check and creates a dynamic “Update available” button. |
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp |
Same main-menu integration for Zero Hour. |
Generals/Code/GameEngine/Include/Common/GlobalData.h |
Adds m_checkForUpdates toggle. |
Generals/Code/GameEngine/Source/Common/GlobalData.cpp |
Parses CheckForUpdates from Options.ini and sets default TRUE. |
GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h |
Adds m_checkForUpdates toggle (ZH). |
GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp |
Parses CheckForUpdates from Options.ini and sets default TRUE (ZH). |
cmake/config-build.cmake |
Adds SAGE_UPDATE_CHECK option and compile definition; auto-enables with SDL3. |
cmake/curl.cmake |
find_package(CURL CONFIG REQUIRED) when update-check enabled. |
CMakeLists.txt |
Includes cmake/curl.cmake. |
CMakePresets.json |
Enables SAGE_UPDATE_CHECK in linux/macOS presets. |
vcpkg.json |
Adds curl dependency for non-Windows builds. |
flatpak/com.fbraz3.GeneralsX.yml |
Enables SAGE_UPDATE_CHECK for flatpak build. |
flatpak/com.fbraz3.GeneralsXZH.yml |
Enables SAGE_UPDATE_CHECK for flatpak build. |
resources/gitinfo/git_watcher.cmake |
Uses git describe --exact-match --tags so GitTag is set only on tagged commits. |
.github/workflows/release.yml |
Adds a pre-build tag creation job and tag cleanup on failure. |
AGENTS.md / CLAUDE.md |
Renames agent instructions file to AGENTS.md. |
.vscode/extensions.json |
Updates VS Code extension recommendations. |
docs/DEV_BLOG/2026-04-DIARY.md |
Adds diary entry describing the change. |
Generals/Code/GameEngine/CMakeLists.txt |
Notes UpdateChecker now lives in Core (no functional change). |
GeneralsMD/Code/GameEngine/CMakeLists.txt |
Notes UpdateChecker now lives in Core (no functional change). |
Generals/Code/GameEngineDevice/CMakeLists.txt |
Comment about curl transitive linkage. |
GeneralsMD/Code/GameEngineDevice/CMakeLists.txt |
Comment about curl transitive linkage. |
- UpdateChecker: init s_done to 1 so poll() returns false on early exit
- UpdateChecker: add CURLOPT_NOSIGNAL=1L for POSIX thread safety
- UpdateChecker: call curl_global_init on main thread before thread creation
- UpdateChecker: call SDL_DetachThread immediately after SDL_CreateThread
- UpdateChecker: replace semver comparison with date-based logic using
published_at vs GitCommitTimeStamp (parseISO8601 + timegm/_mkgmtime)
- config-build.cmake: use if(NOT DEFINED CACHE{}) pattern so explicit
-DSAGE_UPDATE_CHECK=OFF is respected
- release.yml: move dry_run guard to step level in create-tag job so
build jobs are never skipped on dry runs
- release.yml: remove prepare-release from cleanup-tag-on-failure needs
and use always() with explicit per-job failure checks
- MainMenu.cpp (Generals + ZH): destroy and null updateNotifyButton in
MainMenuShutdown to prevent dangling window handle
- vcpkg-lock.json: add curl 8.13.0 entry (baseline arm64-osx)
- AGENTS.md: fix stray space in git-commit.instructions.md reference
47775a2 to
84e0a2a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements a background update checker for both GeneralsX and GeneralsXZH, using libcurl + SDL thread. On tagged release builds, the game checks the latest release from the GitHub API in the background and shows a notification button at the bottom-right corner of the main menu if a newer version is available.
Closes #51
What Changed
New files
cmake/curl.cmake— libcurl dependency (vcpkg, sectransp SSL on macOS)Generals/Code/GameEngine/Include/Common/UpdateChecker.hGenerals/Code/GameEngine/Source/Common/UpdateChecker.cppGeneralsMD/Code/GameEngine/Include/Common/UpdateChecker.hGeneralsMD/Code/GameEngine/Source/Common/UpdateChecker.cppUpdated files
vcpkg.json— added libcurl 8.13.0 dependencyCMakePresets.json+cmake/config-build.cmake—SAGE_UPDATE_CHECKflag (auto-ON withSAGE_USE_SDL3)Generals/Code/GameEngine/CMakeLists.txt+GeneralsMD/Code/GameEngine/CMakeLists.txtGenerals/Code/GameEngineDevice/CMakeLists.txt+GeneralsMD/Code/GameEngineDevice/CMakeLists.txtGenerals/Code/GameEngine/Include/Common/GlobalData.h+.cpp—m_checkForUpdatesfieldGeneralsMD/Code/GameEngine/Include/Common/GlobalData.h+.cpp— sameMainMenu.cppfiles — dynamic button creation + click handlerflatpak/com.fbraz3.GeneralsX.yml+com.fbraz3.GeneralsXZH.yml— curl module addedCLAUDE.md→AGENTS.mdrename for AI agent compatibility.vscode/extensions.json— extension recommendations updatedImplementation Details
UpdateChecker
SDL_CreateThreadthread to avoid blocking game startupSDL_AtomicIntfor thread-safe result communicationGitTag != ""andGitUncommittedChanges == false)GENERALS_FORCE_UPDATE_CHECKenv var bypasses tag check for testingUI
ButtonGetUpdate(WND position622,32at800x600— behind logo at modern resolutions) with a dynamically created button viagogoGadgetPushButtonTheDisplay->getWidth() - 260 - 8,getHeight() - 26 - 8(bottom-right, resolution-aware)winFindFont("Arial", 10, FALSE)— replaces oversized WND default"Update available: vX.X""Update available: vX.X - click to download"SDL_OpenURL("https://github.com/fbraz3/GeneralsX/releases")Testing
Tested on macOS (ARM64,
macos-vulkanpreset) with:Button appears at bottom-right ~8s after game launch with correct text, font, and click behavior.