fix: support extended GeneralsOnline version format with build suffixes#296
fix: support extended GeneralsOnline version format with build suffixes#296undead2146 wants to merge 7 commits into
Conversation
The Generals Online CDN changed the version format from MMDDYY_QFE# to MMDDYY_QFE#_SUFFIX (e.g. "042826_QFE3_EAC"). The existing parser required exactly 2 underscore-delimited segments, causing update detection and version comparison to silently fail. Changes: - GameVersionHelper.ParseGeneralsOnlineVersion: accept 2+ segments (was: exactly 2). Extra segments are build metadata, ignored for date+QFE comparison. - VersionComparer: route GeneralsOnline publisher through dedicated CompareGeneralsOnlineVersions that parses the structured format before falling back to numeric comparison. - Update doc comments on version models and constants to document the extended format. - Add GameVersionHelperTests covering legacy, extended, and edge cases. - Add VersionComparer extended-format theory and catalog parser tests for manifest.json and latest.txt with the new format. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
When only one version parses (e.g. "042826_QFE3_EAC" vs "Unknown"), the previous fallback to CompareNumericVersions could produce counter-intuitive ordering via string-ordinal comparison. Now explicitly treats unparseable versions as always older, and only falls back to numeric comparison when both are unparseable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…rsion The dateValue*10+QFE encoding limits QFE to 0-9 without collision. This is intentional to preserve manifest ID stability — changing it would break installed content references. Version ordering uses ParseGeneralsOnlineVersion directly via CompareGeneralsOnlineVersions, which has no such limitation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded "QFE" literal in ParseGeneralsOnlineVersion with the existing GeneralsOnlineConstants.QfeMarkerPrefix constant for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add detection for plugins/ directory during manifest generation - Include plugin files (EAC DLLs) in GameClient manifests - Plugin files are tracked with InstallTarget.Workspace - Fixes 'Failed to load the AntiCheat plugin' error for _EAC versions - Backward compatible: no plugins dir = no files added This ensures EAC DLLs are properly copied to the workspace during game launch, resolving the incomplete plugin path error.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs (1)
458-466: 🧹 Nitpick | 🔵 Trivial | 💤 Low value
isPluginis destructured but never referenced in either manifest-construction loop.In the MapPack loop it is a pure dead variable (replace with
_). In the GameClient loop it is also never tested — plugin files already reachmanifestFiles.Addvia the existing "skip maps only" path, so theisPluginflag has no behavioral effect on manifest output (only on the earlier debug log at line 445).If plugin files genuinely need different treatment in the future (e.g., a distinct
InstallTarget), the logic should be added here; otherwise use discards to make the intent explicit and suppress any compiler warnings.♻️ Proposed refactor — use discards where `isPlugin` is unused
- foreach (var (relativePath, fileInfo, hash, isMap, isPlugin) in filesWithHashes) + foreach (var (relativePath, fileInfo, hash, isMap, _) in filesWithHashes) { if (!isMap) { continue; } manifestFiles.Add(CreateMapManifestFile(relativePath, fileInfo, hash)); }- foreach (var (relativePath, fileInfo, hash, isMap, isPlugin) in filesWithHashes) + foreach (var (relativePath, fileInfo, hash, isMap, _) in filesWithHashes) { // ... existing logic unchanged }Also applies to: 477-507
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs` around lines 458 - 466, The destructured variable isPlugin in the filesWithHashes loops is never used; update the tuple deconstruction to discard that element (use _) in both the MapPack loop that calls CreateMapManifestFile and the GameClient loop that adds to manifestFiles so the intent is explicit and compiler warnings are suppressed; if special handling for plugins is required later, add the conditional logic around isPlugin where manifestFiles.Add or CreateMapManifestFile is invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs`:
- Around line 417-419: The plugins directory string is hardcoded in
GeneralsOnlineManifestFactory (pluginsDirectory) while mapsDirectory uses
GeneralsOnlineConstants.MapsSubdirectory; add a new constant PluginsSubdirectory
to GeneralsOnlineConstants and replace the literal "plugins" in
GeneralsOnlineManifestFactory with GeneralsOnlineConstants.PluginsSubdirectory
so both directories follow the same pattern and are maintained centrally.
---
Outside diff comments:
In
`@GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs`:
- Around line 458-466: The destructured variable isPlugin in the filesWithHashes
loops is never used; update the tuple deconstruction to discard that element
(use _) in both the MapPack loop that calls CreateMapManifestFile and the
GameClient loop that adds to manifestFiles so the intent is explicit and
compiler warnings are suppressed; if special handling for plugins is required
later, add the conditional logic around isPlugin where manifestFiles.Add or
CreateMapManifestFile is invoked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b6103e60-bf2c-41a6-a4e5-7e1e4a033ca4
📒 Files selected for processing (1)
GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs
1. GenHub/GenHub....
|
I'd move the EAC plugin work to another PR, make sure there is a real behavioral change, add tests, and try it to make sure it actually works. |
- Move EAC plugin work out of this PR (revert isPlugin tracking, PluginsSubdirectory constant, plugins directory detection). These will be reintroduced in a dedicated PR with real behavioral change, tests, and verified end-to-end execution. - Fix inconsistent brace usage in single-line if blocks in VersionComparer.cs (all ifs now use braces). All 1232 Core tests pass. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>


Summary
MMDDYY_QFE#(2 segments) toMMDDYY_QFE#_SUFFIX(3+ segments, e.g."042826_QFE3_EAC").ParseGeneralsOnlineVersionrequired exactly 2 underscore-delimited parts, so it returnednullfor all new versions — update detection, version comparison, and sortable version IDs all silently broke.CompareGeneralsOnlineVersionsmethod inVersionComparerthat uses the structured parser instead of generic numeric comparison, ensuring"042826_QFE2"and"042826_QFE2_EAC"compare as equal.Changes
GameVersionHelper.csparts.Length != 2→parts.Length < 2— accept extended formatVersionComparer.csCompareGeneralsOnlineVersions()— structured date+QFE comparison with fallbackGeneralsOnlineUpdateService.csGeneralsOnlineApiResponse.csGeneralsOnlineRelease.csGeneralsOnlineConstants.csTests added
GameVersionHelperTests.cs(new)VersionComparerTests.csGeneralsOnlineJsonCatalogParserTests.csTest plan
MMDDYY_QFE#versions still parse and compare correctlyMMDDYY_QFE#_SUFFIXversions parse date+QFE correctly🤖 Generated with Claude Code
🤖 OpenClaw Changes
Files changed: 2
Lines added: 7
Lines removed: 1
Branch:
fix/go-version-extended-formatCommit:
c88bdda0Last updated: 2026-05-04T09:14:14.969Z
Greptile Summary
This PR fixes a version parsing regression caused by the Generals Online CDN switching from a 2-segment format (
MMDDYY_QFE#) to a 3+-segment format (MMDDYY_QFE#_SUFFIX). The single-character guard change (!= 2→< 2) inParseGeneralsOnlineVersionrestores update detection, and the newCompareGeneralsOnlineVersionsmethod inVersionComparerensures structured date+QFE ordering is used instead of opaque numeric fallback.GameVersionHelper.cs: Relaxes the segment-count guard from exactly 2 to at least 2, and replaces the hardcoded\"QFE\"literal withGeneralsOnlineConstants.QfeMarkerPrefix, aligning with the project's constants convention.VersionComparer.cs: IntroducesCompareGeneralsOnlineVersionswith explicit handling for both-null (generic fallback), single-null (unparseable treated as older), and fully-parsed (date then QFE) cases — directly addressing the asymmetric fallback identified in a prior review.Confidence Score: 5/5
Safe to merge — the change is a minimal, well-tested relaxation of a length guard that restores broken version detection for the new CDN format.
The one-character logic fix is narrow and its correctness is directly validated by 20 new tests covering legacy, extended, multi-suffix, and edge-case inputs. The new CompareGeneralsOnlineVersions method explicitly handles all null-combination states, closing the asymmetric-fallback gap flagged in a prior review. No public API signatures changed, backward compatibility with existing 2-segment versions is confirmed, and the previously-hardcoded QFE literal is now correctly referenced through the constants class.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["Version string input\ne.g. 042826_QFE3_EAC"] --> B["Split on underscore\nRemoveEmptyEntries"] B --> C{parts.Length less than 2?} C -- Yes --> D["Return null"] C -- No --> E["datePart = parts 0\nqfePart = parts 1 minus QFE prefix"] E --> F{Valid 6-char date\nand numeric QFE?} F -- No --> D F -- Yes --> G["Return DateTime + Qfe\nExtra suffix parts ignored"] G --> H["CompareGeneralsOnlineVersions"] H --> I{Parsed results?} I -- Both null --> J["CompareNumericVersions fallback"] I -- One null --> K["Null treated as older"] I -- Both parsed --> L["Compare Date then QFE"]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A["Version string input\ne.g. 042826_QFE3_EAC"] --> B["Split on underscore\nRemoveEmptyEntries"] B --> C{parts.Length less than 2?} C -- Yes --> D["Return null"] C -- No --> E["datePart = parts 0\nqfePart = parts 1 minus QFE prefix"] E --> F{Valid 6-char date\nand numeric QFE?} F -- No --> D F -- Yes --> G["Return DateTime + Qfe\nExtra suffix parts ignored"] G --> H["CompareGeneralsOnlineVersions"] H --> I{Parsed results?} I -- Both null --> J["CompareNumericVersions fallback"] I -- One null --> K["Null treated as older"] I -- Both parsed --> L["Compare Date then QFE"]Reviews (7): Last reviewed commit: "OpenClaw: Address review feedback from @..." | Re-trigger Greptile