fix: support extended GeneralsOnline version format with build suffixes#296
fix: support extended GeneralsOnline version format with build suffixes#296undead2146 wants to merge 6 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....


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 parse failure introduced by the Generals Online CDN switching from
MMDDYY_QFE#(2 segments) toMMDDYY_QFE#_SUFFIX(3+ segments, e.g."042826_QFE3_EAC"). The one-line guard change inParseGeneralsOnlineVersion(!= 2→< 2) restores correct parsing, and the newCompareGeneralsOnlineVersionsmethod inVersionComparerreplaces the former generic numeric fallback with structured date+QFE comparison — including proper null-case handling for mixed parseable/unparseable inputs (the previously flagged asymmetric fallback issue is fully addressed here). Test coverage is thorough across all new and existing paths.Confidence Score: 5/5
Safe to merge — all logic changes are correct, backward compatible, and well-tested.
No P0 or P1 issues found. The core parser change is minimal and correct. The new comparison method handles all null combinations properly. The isPlugin flag in the manifest factory is intentionally diagnostic-only and clearly documented. All 53 tests pass including 20 new cases covering the edge conditions.
No files require special attention.
Important Files Changed
parts.Length != 2changed toparts.Length < 2inParseGeneralsOnlineVersion; hardcoded"QFE"replaced withGeneralsOnlineConstants.QfeMarkerPrefix. Logic is correct and handles all edge cases via the surrounding try-catch.CompareGeneralsOnlineVersionsprivate method correctly handles all null combinations (both null → numeric fallback, one null → treat as older, both parsed → date-then-QFE). Addresses the previously flagged asymmetric fallback.isPluginflag andPluginsSubdirectorydetection for EAC files. The flag is used only inLogDebug; plugin files are routed to GameClient manifests withWorkspaceinstall target, consistent with all other non-map files. Intent is clearly documented in comments.PluginsSubdirectory = "plugins"constant and a remark onQfeSeparatorclarifying catalog-parser vs. comparison usage. Consistent with the constants style guide.MMddyy_QFE#toMMddyy_QFE#[_SUFFIX]. No logic changes.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[CompareVersions called\nwith publisherType=GeneralsOnline] --> B[CompareGeneralsOnlineVersions] B --> C[ParseGeneralsOnlineVersion v1 and v2] C --> D{Both null?} D -- Yes --> E[CompareNumericVersions fallback] D -- No --> F{v1 null?} F -- Yes --> G[return -1: v1 treated as older] F -- No --> H{v2 null?} H -- Yes --> I[return 1: v2 treated as older] H -- No --> J[Compare Date fields] J --> K{Dates equal?} K -- No --> L[return dateCompare] K -- Yes --> M[Compare QFE integers] M --> N[return qfeCompare] subgraph ParseGeneralsOnlineVersion P1[Split on underscore RemoveEmptyEntries] --> P2{parts.Length < 2?} P2 -- Yes --> P3[return null] P2 -- No --> P4[datePart=parts0 qfePart=parts1 strip QFE] P4 --> P5{6-char date AND numeric QFE?} P5 -- No --> P3 P5 -- Yes --> P6[Parse MM DD YY Return Date+QFE tuple] P6 --> P7[Extra suffix segments parts2+ ignored] endReviews (6): Last reviewed commit: "OpenClaw: Address code review feedback f..." | Re-trigger Greptile