diff --git a/GenHub/GenHub.Core/Constants/GeneralsOnlineConstants.cs b/GenHub/GenHub.Core/Constants/GeneralsOnlineConstants.cs
index 242cb02ef..0179d126c 100644
--- a/GenHub/GenHub.Core/Constants/GeneralsOnlineConstants.cs
+++ b/GenHub/GenHub.Core/Constants/GeneralsOnlineConstants.cs
@@ -52,6 +52,7 @@ public static class GeneralsOnlineConstants
public const string VersionDateFormat = "MMddyy";
/// Separator between date and QFE number in versions.
+ /// Used by catalog parser for date extraction. Version comparison uses underscore split.
public const string QfeSeparator = "_QFE";
/// Prefix for QFE markers in version strings.
diff --git a/GenHub/GenHub.Core/Helpers/GameVersionHelper.cs b/GenHub/GenHub.Core/Helpers/GameVersionHelper.cs
index 60061eff6..4a991bd20 100644
--- a/GenHub/GenHub.Core/Helpers/GameVersionHelper.cs
+++ b/GenHub/GenHub.Core/Helpers/GameVersionHelper.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Text.RegularExpressions;
+using GenHub.Core.Constants;
namespace GenHub.Core.Helpers;
@@ -122,7 +123,9 @@ public static int NormalizeVersion(string? version)
}
///
- /// Parses a version string (MMDDYY_QFE#) used by Generals Online.
+ /// Parses a version string (MMDDYY_QFE#[_SUFFIX]) used by Generals Online.
+ /// Supports extended formats like "042826_QFE3_EAC" — extra segments after
+ /// the QFE number are build metadata and do not affect version ordering.
///
/// The version string to parse.
/// A tuple containing the extracted date and QFE number, or null if parsing fails.
@@ -135,15 +138,15 @@ public static (DateTime Date, int Qfe)? ParseGeneralsOnlineVersion(string? versi
try
{
- // Format: MMDDYY_QFE# or DDMMYY_QFE# (General Online CDN uses MMDDYY)
+ // Format: MMDDYY_QFE#[_SUFFIX...] (Generals Online CDN uses MMDDYY)
var parts = version.Split('_', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- if (parts.Length != 2)
+ if (parts.Length < 2)
{
return null;
}
var datePart = parts[0];
- var qfePart = parts[1].Replace("QFE", string.Empty, StringComparison.OrdinalIgnoreCase);
+ var qfePart = parts[1].Replace(GeneralsOnlineConstants.QfeMarkerPrefix, string.Empty, StringComparison.OrdinalIgnoreCase);
if (datePart.Length != 6 || !int.TryParse(qfePart, out var qfe))
{
@@ -164,7 +167,15 @@ public static (DateTime Date, int Qfe)? ParseGeneralsOnlineVersion(string? versi
///
/// Gets a sortable integer version for Generals Online versions.
- /// Converts "101525_QFE2" to 1015252.
+ /// Converts "101525_QFE2" to 1015252, "042826_QFE3_EAC" to 428263.
+ /// Build suffixes (e.g. _EAC) are ignored — only date and QFE affect ordering.
+ ///
+ /// Note: The encoding uses dateValue * 10 + QFE, which limits QFE to 0–9
+ /// without colliding with the next date. This is intentional — the value is embedded
+ /// in manifest IDs and changing the encoding would break installed content references.
+ /// Version ordering uses directly via
+ /// VersionComparer.CompareGeneralsOnlineVersions, which has no such limitation.
+ ///
///
/// The version string to convert.
/// A sortable integer, or 0 if parsing fails.
diff --git a/GenHub/GenHub.Core/Helpers/VersionComparer.cs b/GenHub/GenHub.Core/Helpers/VersionComparer.cs
index a3d1dc208..0b979e9e5 100644
--- a/GenHub/GenHub.Core/Helpers/VersionComparer.cs
+++ b/GenHub/GenHub.Core/Helpers/VersionComparer.cs
@@ -22,11 +22,19 @@ public static int CompareVersions(string? version1, string? version2, string? pu
{
// Handle null/empty cases
if (string.IsNullOrWhiteSpace(version1) && string.IsNullOrWhiteSpace(version2))
+ {
return 0;
+ }
+
if (string.IsNullOrWhiteSpace(version1))
+ {
return -1;
+ }
+
if (string.IsNullOrWhiteSpace(version2))
+ {
return 1;
+ }
// Determine comparison strategy based on publisher type
if (string.Equals(publisherType, CommunityOutpostConstants.PublisherType, StringComparison.OrdinalIgnoreCase))
@@ -41,14 +49,53 @@ public static int CompareVersions(string? version1, string? version2, string? pu
}
else if (string.Equals(publisherType, PublisherTypeConstants.GeneralsOnline, StringComparison.OrdinalIgnoreCase))
{
- // GeneralsOnline uses numeric versions
- return CompareNumericVersions(version1, version2);
+ // GeneralsOnline uses MMDDYY_QFE#[_SUFFIX] format — parse for accurate comparison
+ return CompareGeneralsOnlineVersions(version1, version2);
}
// Default: Use the robust numeric/semantic comparison logic
return CompareNumericVersions(version1, version2);
}
+ ///
+ /// Compares two GeneralsOnline version strings (MMDDYY_QFE#[_SUFFIX]).
+ /// Extra suffix segments (e.g. _EAC) are build metadata and do not affect ordering.
+ /// Falls back to when parsing fails.
+ ///
+ /// The first version.
+ /// The second version.
+ /// Comparison result.
+ private static int CompareGeneralsOnlineVersions(string version1, string version2)
+ {
+ var parsed1 = GameVersionHelper.ParseGeneralsOnlineVersion(version1);
+ var parsed2 = GameVersionHelper.ParseGeneralsOnlineVersion(version2);
+
+ // Both unparseable — fall back to generic numeric comparison
+ if (parsed1 == null && parsed2 == null)
+ {
+ return CompareNumericVersions(version1, version2);
+ }
+
+ // Exactly one unparseable — treat unparseable as always older
+ if (parsed1 == null)
+ {
+ return -1;
+ }
+
+ if (parsed2 == null)
+ {
+ return 1;
+ }
+
+ int dateCompare = parsed1.Value.Date.CompareTo(parsed2.Value.Date);
+ if (dateCompare != 0)
+ {
+ return dateCompare;
+ }
+
+ return parsed1.Value.Qfe.CompareTo(parsed2.Value.Qfe);
+ }
+
///
/// Compares two date-based version strings in YYYY-MM-DD format.
///
@@ -170,22 +217,30 @@ private static int CompareNumericVersions(string ver1, string ver2)
private static string NormalizeVersionString(string version)
{
if (string.IsNullOrWhiteSpace(version))
+ {
return string.Empty;
+ }
// Remove common prefixes that publishers use
var normalized = version;
// Remove weekly- prefix (SuperHackers)
if (normalized.StartsWith("weekly-", StringComparison.OrdinalIgnoreCase))
+ {
normalized = normalized.Substring(8); // Remove "weekly-"
+ }
// Remove release- prefix
if (normalized.StartsWith("release-", StringComparison.OrdinalIgnoreCase))
+ {
normalized = normalized.Substring(9); // Remove "release-"
+ }
// Remove version- prefix
if (normalized.StartsWith("version-", StringComparison.OrdinalIgnoreCase))
+ {
normalized = normalized.Substring(9); // Remove "version-"
+ }
// Remove 'v' or 'V' prefix
normalized = normalized.TrimStart('v', 'V');
@@ -237,13 +292,19 @@ private static int CompareSemanticVersions(string ver1, string ver2)
if (isDigit1 && isDigit2 && long.TryParse(segment1, out var num1) && long.TryParse(segment2, out var num2))
{
- if (num1 != num2) return num1.CompareTo(num2);
+ if (num1 != num2)
+ {
+ return num1.CompareTo(num2);
+ }
}
else
{
// For non-pure-numeric segments, compare the full original strings (case-insensitive)
var strCompare = string.Compare(rawSegment1, rawSegment2, StringComparison.OrdinalIgnoreCase);
- if (strCompare != 0) return strCompare;
+ if (strCompare != 0)
+ {
+ return strCompare;
+ }
}
}
@@ -277,7 +338,9 @@ private static bool TryParseDateVersion(string dateStr, out DateTime result)
private static long? ExtractNumericFromDate(string dateStr)
{
if (string.IsNullOrWhiteSpace(dateStr))
+ {
return null;
+ }
// Remove common date separators
var digits = dateStr.Replace("-", string.Empty)
@@ -298,7 +361,9 @@ private static bool TryParseDateVersion(string dateStr, out DateTime result)
private static string ExtractDigits(string version)
{
if (string.IsNullOrWhiteSpace(version))
+ {
return string.Empty;
+ }
var result = new System.Text.StringBuilder();
foreach (var c in version)
diff --git a/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineApiResponse.cs b/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineApiResponse.cs
index a4cb03730..a8ce32557 100644
--- a/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineApiResponse.cs
+++ b/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineApiResponse.cs
@@ -8,8 +8,9 @@ namespace GenHub.Core.Models.GeneralsOnline;
public class GeneralsOnlineApiResponse
{
///
- /// Gets or sets the version string (e.g., "111825_QFE2" for November 18, 2025).
- /// Format: MMDDYY_QFE# where MM=month, DD=day, YY=year, #=QFE number.
+ /// Gets or sets the version string (e.g., "111825_QFE2" or "042826_QFE3_EAC").
+ /// Format: MMDDYY_QFE#[_SUFFIX] where MM=month, DD=day, YY=year, #=QFE number.
+ /// The optional suffix (e.g. "_EAC") is build metadata that does not affect version ordering.
///
[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;
diff --git a/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineRelease.cs b/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineRelease.cs
index fd42ee2b2..8027f564e 100644
--- a/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineRelease.cs
+++ b/GenHub/GenHub.Core/Models/GeneralsOnline/GeneralsOnlineRelease.cs
@@ -10,7 +10,7 @@ namespace GenHub.Core.Models.GeneralsOnline;
public class GeneralsOnlineRelease
{
///
- /// Gets version string in format: MMDDYY_QFE# (e.g., "101525_QFE5").
+ /// Gets version string in format: MMDDYY_QFE#[_SUFFIX] (e.g., "101525_QFE5", "042826_QFE3_EAC").
///
public required string Version { get; init; }
diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Content/Services/GeneralsOnline/GeneralsOnlineJsonCatalogParserTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Content/Services/GeneralsOnline/GeneralsOnlineJsonCatalogParserTests.cs
index e2e10254f..ef1e6dd44 100644
--- a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Content/Services/GeneralsOnline/GeneralsOnlineJsonCatalogParserTests.cs
+++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Content/Services/GeneralsOnline/GeneralsOnlineJsonCatalogParserTests.cs
@@ -1,5 +1,6 @@
using GenHub.Core.Constants;
using GenHub.Core.Interfaces.Providers;
+using GenHub.Core.Models.GeneralsOnline;
using GenHub.Core.Models.Providers;
using GenHub.Features.Content.Services.GeneralsOnline;
using Microsoft.Extensions.Logging.Abstractions;
@@ -91,4 +92,51 @@ public async Task ParseAsync_WithCamelCaseJson_ParsesCorrectly()
var item = result.Data.First();
Assert.Equal("111825_QFE2", item.Version);
}
+
+ ///
+ /// Tests that ParseAsync correctly parses extended version format with build suffix.
+ ///
+ /// A representing the asynchronous test operation.
+ [Fact]
+ public async Task ParseAsync_WithExtendedVersion_ParsesCorrectly()
+ {
+ // Arrange
+ var json = @"{
+ ""version"": ""042826_QFE3_EAC"",
+ ""download_url"": ""https://cdn.playgenerals.online/GeneralsOnline_portable_042826_QFE3_EAC.zip"",
+ ""size"": 28277693,
+ ""release_notes"": ""www.playgenerals.online/updates"",
+ ""sha256"": ""abc123""
+ }";
+
+ var wrapper = $"{{\"source\":\"manifest\",\"data\":{json}}}";
+
+ // Act
+ var result = await _parser.ParseAsync(wrapper, _provider);
+
+ // Assert
+ Assert.True(result.Success);
+ var item = result.Data!.First();
+ Assert.Equal("042826_QFE3_EAC", item.Version);
+ Assert.Equal("https://cdn.playgenerals.online/GeneralsOnline_portable_042826_QFE3_EAC.zip", item.GetData()!.PortableUrl);
+ }
+
+ ///
+ /// Tests that ParseAsync correctly handles latest.txt fallback with extended version.
+ ///
+ /// A representing the asynchronous test operation.
+ [Fact]
+ public async Task ParseAsync_LatestFallbackExtendedVersion_ParsesCorrectly()
+ {
+ // Arrange
+ var wrapper = "{\"source\":\"latest\",\"version\":\"042826_QFE3_EAC\"}";
+
+ // Act
+ var result = await _parser.ParseAsync(wrapper, _provider);
+
+ // Assert
+ Assert.True(result.Success);
+ var item = result.Data!.First();
+ Assert.Equal("042826_QFE3_EAC", item.Version);
+ }
}
diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/GameVersionHelperTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/GameVersionHelperTests.cs
new file mode 100644
index 000000000..bc614fccd
--- /dev/null
+++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/GameVersionHelperTests.cs
@@ -0,0 +1,134 @@
+using GenHub.Core.Helpers;
+
+namespace GenHub.Tests.Core.Helpers;
+
+///
+/// Tests for Generals Online version parsing.
+///
+public class GameVersionHelperTests
+{
+ // ==== ParseGeneralsOnlineVersion — legacy format (MMDDYY_QFE#) ====
+
+ ///
+ /// Verifies that legacy two-segment versions parse correctly.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_LegacyFormat_ParsesCorrectly()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("101525_QFE2");
+
+ Assert.NotNull(result);
+ Assert.Equal(new DateTime(2025, 10, 15), result.Value.Date);
+ Assert.Equal(2, result.Value.Qfe);
+ }
+
+ // ==== ParseGeneralsOnlineVersion — extended format (MMDDYY_QFE#_SUFFIX) ====
+
+ ///
+ /// Verifies that extended format with build suffix parses date and QFE correctly.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_ExtendedFormat_ParsesDateAndQfe()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("042826_QFE3_EAC");
+
+ Assert.NotNull(result);
+ Assert.Equal(new DateTime(2026, 4, 28), result.Value.Date);
+ Assert.Equal(3, result.Value.Qfe);
+ }
+
+ ///
+ /// Verifies that versions with multiple suffix segments still parse correctly.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_MultipleSuffixes_ParsesDateAndQfe()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("011526_QFE1_EAC_X86");
+
+ Assert.NotNull(result);
+ Assert.Equal(new DateTime(2026, 1, 15), result.Value.Date);
+ Assert.Equal(1, result.Value.Qfe);
+ }
+
+ // ==== ParseGeneralsOnlineVersion — edge cases ====
+
+ ///
+ /// Verifies that null or empty versions return null.
+ ///
+ /// The version string to test.
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ParseGeneralsOnlineVersion_NullOrEmpty_ReturnsNull(string? version)
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion(version);
+ Assert.Null(result);
+ }
+
+ ///
+ /// Verifies that versions missing the underscore separator return null.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_NoUnderscore_ReturnsNull()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("042826");
+ Assert.Null(result);
+ }
+
+ ///
+ /// Verifies that versions with malformed dates return null.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_InvalidDate_ReturnsNull()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("ABCDEF_QFE1");
+ Assert.Null(result);
+ }
+
+ ///
+ /// Verifies that versions with non-numeric QFE return null.
+ ///
+ [Fact]
+ public void ParseGeneralsOnlineVersion_NonNumericQfe_ReturnsNull()
+ {
+ var result = GameVersionHelper.ParseGeneralsOnlineVersion("042826_QFEx");
+ Assert.Null(result);
+ }
+
+ // ==== GetGeneralsOnlineSortableVersion ====
+
+ ///
+ /// Verifies that sortable version is computed correctly for legacy format.
+ ///
+ [Fact]
+ public void GetGeneralsOnlineSortableVersion_LegacyFormat_ReturnsCorrectValue()
+ {
+ // "101525_QFE2" -> date=101525, qfe=2 -> 101525*10 + 2 = 1015252
+ var result = GameVersionHelper.GetGeneralsOnlineSortableVersion("101525_QFE2");
+ Assert.Equal(1015252, result);
+ }
+
+ ///
+ /// Verifies that sortable version ignores build suffixes in extended format.
+ ///
+ [Fact]
+ public void GetGeneralsOnlineSortableVersion_ExtendedFormat_IgnoresSuffix()
+ {
+ // "042826_QFE3_EAC" -> date=042826, qfe=3 -> 42826*10 + 3 = 428263
+ var result = GameVersionHelper.GetGeneralsOnlineSortableVersion("042826_QFE3_EAC");
+ Assert.Equal(428263, result);
+ }
+
+ ///
+ /// Verifies that legacy and extended formats with same date+QFE produce the same sortable value.
+ ///
+ [Fact]
+ public void GetGeneralsOnlineSortableVersion_SameVersionDifferentFormat_ProducesEqualValue()
+ {
+ var legacy = GameVersionHelper.GetGeneralsOnlineSortableVersion("042826_QFE3");
+ var extended = GameVersionHelper.GetGeneralsOnlineSortableVersion("042826_QFE3_EAC");
+
+ Assert.Equal(legacy, extended);
+ }
+}
diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/VersionComparerTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/VersionComparerTests.cs
index ba59c408e..d3d393eea 100644
--- a/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/VersionComparerTests.cs
+++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Helpers/VersionComparerTests.cs
@@ -169,4 +169,67 @@ public void CompareVersions_UnknownPublisher_NumericExtraction_ReturnsCorrectCom
// Assert
Assert.Equal(expected, Math.Sign(result));
}
+
+ ///
+ /// Verifies that GeneralsOnline versions with extended format (build suffix) compare correctly.
+ /// Versions like "042826_QFE3_EAC" should compare by date+QFE only, ignoring the suffix.
+ ///
+ /// The first version string.
+ /// The second version string.
+ /// The expected comparison result.
+ [Theory]
+ [InlineData("042826_QFE3_EAC", "042826_QFE3_EAC", 0)] // Same extended version
+ [InlineData("042826_QFE4_EAC", "042826_QFE3_EAC", 1)] // Same date, higher QFE
+ [InlineData("042826_QFE3_EAC", "042826_QFE2", 1)] // Extended vs legacy, higher QFE
+ [InlineData("042826_QFE2", "042826_QFE2_EAC", 0)] // Legacy vs extended, same date+QFE
+ [InlineData("042926_QFE1_EAC", "042826_QFE3_EAC", 1)] // Newer date wins regardless of QFE
+ public void CompareVersions_GeneralsOnline_ExtendedFormat_ReturnsCorrectComparison(
+ string version1,
+ string version2,
+ int expected)
+ {
+ // Act
+ var result = VersionComparer.CompareVersions(version1, version2, PublisherTypeConstants.GeneralsOnline);
+
+ // Assert
+ Assert.Equal(expected, Math.Sign(result));
+ }
+
+ ///
+ /// Verifies that when only one GeneralsOnline version is parseable,
+ /// the unparseable version is always treated as older.
+ ///
+ /// The first version string.
+ /// The second version string.
+ /// The expected comparison result.
+ [Theory]
+ [InlineData("042826_QFE3_EAC", "Unknown", 1)] // Parseable vs unparseable
+ [InlineData("Unknown", "042826_QFE3_EAC", -1)] // Unparseable vs parseable
+ [InlineData("042826_QFE2", "garbage", 1)] // Legacy parseable vs unparseable
+ [InlineData("garbage", "042826_QFE2", -1)] // Unparseable vs legacy parseable
+ public void CompareVersions_GeneralsOnline_MixedParseable_UnparseableIsOlder(
+ string version1,
+ string version2,
+ int expected)
+ {
+ // Act
+ var result = VersionComparer.CompareVersions(version1, version2, PublisherTypeConstants.GeneralsOnline);
+
+ // Assert
+ Assert.Equal(expected, Math.Sign(result));
+ }
+
+ ///
+ /// Verifies that when both GeneralsOnline versions are unparseable,
+ /// the method falls back to numeric/string comparison.
+ ///
+ [Fact]
+ public void CompareVersions_GeneralsOnline_BothUnparseable_FallsBackToNumeric()
+ {
+ // Act
+ var result = VersionComparer.CompareVersions("Unknown", "Unknown", PublisherTypeConstants.GeneralsOnline);
+
+ // Assert — same unparseable strings should compare as equal
+ Assert.Equal(0, result);
+ }
}
diff --git a/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs b/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs
index 3afbac6c3..8417cfea3 100644
--- a/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs
+++ b/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineManifestFactory.cs
@@ -408,6 +408,8 @@ private async Task> UpdateManifestsWithExtractedFiles(
var allFiles = Directory.GetFiles(extractPath, "*", SearchOption.AllDirectories);
logger.LogInformation("Processing {Count} files", allFiles.Length);
+ // Track file metadata for manifest generation. Maps are routed to the MapPack
+ // manifest; all other files (executables, shared files) go to the GameClient manifest.
List<(string RelativePath, FileInfo FileInfo, string Hash, bool IsMap)> filesWithHashes = [];
// Detect Maps directory (case-insensitive)
@@ -462,8 +464,8 @@ private async Task> UpdateManifestsWithExtractedFiles(
}
else
{
- // Game client manifest: include executables, shared files, AND map files
- // Map files are included with UserMapsDirectory install target so they install to Documents
+ // Game client manifest: include executables and shared files, but NOT maps
+ // Map files are handled by the MapPack manifest
var targetExecutable = GameClientConstants.GeneralsOnline60HzExecutable;
foreach (var (relativePath, fileInfo, hash, isMap) in filesWithHashes)
diff --git a/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineUpdateService.cs b/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineUpdateService.cs
index 15c24c1d0..2a186c4a8 100644
--- a/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineUpdateService.cs
+++ b/GenHub/GenHub/Features/Content/Services/GeneralsOnline/GeneralsOnlineUpdateService.cs
@@ -91,7 +91,7 @@ private static bool IsNewerVersion(string latestVersion, string? currentVersion)
return true; // Any version is newer than nothing
}
- // Parse MMddyy_QFE# format
+ // Parse MMddyy_QFE#[_SUFFIX] format (suffixes like _EAC are build metadata, ignored for comparison)
var latest = GameVersionHelper.ParseGeneralsOnlineVersion(latestVersion);
var current = GameVersionHelper.ParseGeneralsOnlineVersion(currentVersion);