GenHub
FlagshipCross-platform launcher, Content-Addressable mod sandbox, and native GenPatcher compatibility engine for Command & Conquer
1. GameInstallation
Scans Windows registry hives, Steam VDF manifests, and standard Linux Proton/Wine prefixes with SemaphoreSlim caching.
- ▸Locates 6+ distribution types (Steam, EA App, Origin, CD/DVD, First Decade, Custom).
- ▸Validates directory read/write permissions before attempting workspace creation.
- ▸Handles Wine prefix path translation dynamically on Linux.
Problem and motivation
The Command & Conquer: Generals and Zero Hour ecosystem is fragmented across multiple retail distributions: Steam, EA App, Origin, the First Decade DVD, and legacy CD installs.
Each distribution uses different directory structures, registry keys, and binary patch levels (1.04 vs 1.08). Historically, community modifications directly overwrote base game files in the install directory. Switching between total conversions or joining online matches often resulted in multiplayer desynchronization crashes (mismatched GameData.ini and asset archives) and corrupted installations.
Furthermore, running the legacy SAGE engine on modern 64-bit Windows required external patchers like GenPatcher to fix missing DirectX runtimes, registry paths, and broken browser dependencies.
GenHub solves this entire ecosystem problem in a single unified desktop application on Windows and Linux (Wine/Proton) through declarative content manifests, Content-Addressable Storage (CAS) deduplication, decentralized community subscriber catalogs, isolated workspace sandboxes, and a native C# port of GenPatcher.
Seven architectural pillars
GenHub organizes its core architecture into seven decoupled subsystems:
| Pillar | Subsystem | Technical responsibility |
|---|---|---|
| 1. GameInstallation | Installation locator | Scans Windows Registry hives (HKLM\SOFTWARE\WOW6432Node\Electronic Arts), Steam VDF manifests (libraryfolders.vdf), and Linux Proton/Wine prefixes with permission validation and SemaphoreSlim in-memory caching |
| 2. GameClient | Binary & version validator | Identifies the target game executable across 200+ community client builds and validates binary compatibility between the client executable, GameData files, and active manifests |
| 3. ContentManifest & CAS | Declarative packaging | Strongly-typed ManifestId blueprints with SHA-256 Content-Addressable Storage (CAS) deduplication, reference tracking, and automated garbage collection |
| 4. Content Pipeline & Catalogs | Delivery & subscriber catalogs | Multi-stage pipeline (IContentDeliveryOrchestrator -> IContentDeliveryProvider -> IContentDeliverer -> IContentResolver -> ManifestFactory) with decentralized community subscriber catalogs |
| 5. Workspace & Reconciliation | Isolation & delta engine | Strategy pattern execution sandboxes (SymlinkOnly, FullCopy, HybridCopySymlink, HardLink) paired with WorkspaceReconciler for incremental delta updates (+Add, ~Update, -Remove, =Skip) |
| 6. GenPatcher Port & ActionSets | Native compatibility engine | Built-in C# port of the legacy GenPatcher utility applying 25+ automated system fixes, registry repairs, runtime DLLs, and compatibility patches without external tools |
| 7. GameLaunching | Process orchestrator | Per-profile concurrency mutex locks, CAS preflight verification, single-core CPU affinity masking (Process.ProcessorAffinity), and automated post-exit cleanup |
Core engineering mechanics
1. ContentManifest and Content-Addressable Storage (CAS)
Instead of relying on monolithic ZIP archives or destructive file overwrites, GenHub treats every mod, patch, and addon as a declarative ContentManifest.
Files within a manifest are indexed by their SHA-256 hash (CasHash). The local CAS store maintains a deduplicated repository of file blobs in local app data. When multiple community mods share identical texture packs, audio files, or libraries, the CAS store references the existing physical blob without duplicating storage.
View C# implementation: ContentManifest and Content-Addressable Storage
public sealed record ManifestFileEntry(
string RelativePath,
string CasHash,
long FileSize,
FilePermissionFlags Permissions = FilePermissionFlags.ReadOnly
);
public sealed class ContentManifest
{
public required ManifestId Id { get; init; }
public required string DisplayName { get; init; }
public required ContentType Type { get; init; } // BaseGame, Mod, Patch, Addon, Map
public required IReadOnlyList<ManifestFileEntry> Files { get; init; }
public required IReadOnlyList<ManifestId> Dependencies { get; init; }
public required IReadOnlyList<ManifestId> Incompatibilities { get; init; }
}
public sealed class ContentAddressableStorage(IFileSystem fileSystem, string casRootPath) : ICasStorage
{
public async Task<Result<string>> StoreBlobAsync(Stream sourceStream, CancellationToken ct = default)
{
using var sha256 = SHA256.Create();
var tempFile = Path.Combine(casRootPath, "temp", Guid.NewGuid().ToString("N"));
fileSystem.CreateDirectory(Path.GetDirectoryName(tempFile)!);
using (var dest = fileSystem.OpenWrite(tempFile))
{
await sourceStream.CopyToAsync(dest, ct);
}
var hash = ComputeFileSha256(tempFile);
var blobPath = GetBlobPath(hash);
if (!fileSystem.FileExists(blobPath))
{
fileSystem.CreateDirectory(Path.GetDirectoryName(blobPath)!);
fileSystem.MoveFile(tempFile, blobPath);
}
else
{
fileSystem.DeleteFile(tempFile);
}
IncrementReferenceCount(hash);
return Result<string>.Success(hash);
}
public string GetBlobPath(string casHash) =>
Path.Combine(casRootPath, "objects", casHash[..2], casHash[2..4], casHash[4..]);
}
2. Built-in GenPatcher port and ActionSets engine
GenHub natively integrates the fixes previously provided by the standalone GenPatcher utility. Rather than requiring users to run external batch scripts or separate executables, GenHub includes a dedicated IActionSetOrchestrator and modular IActionSet plugins.
The engine detects system environment flaws and applies 25+ automated fixes:
- Runtime fixes: Replaces legacy
BrowserEngine.dllwith backups to eliminate script crash loops, upgradesdbghelp.dllto modern Windows-compatible versions, and verifies DirectX 8/9 runtimes and VC++ redistributables (2005, 2008, 2010). - Registry & path repairs: Rebuilds missing registry paths for EA App, Origin, and The First Decade installs, and resolves Unicode path collisions in user Documents folders (
MyDocumentsPathCompatibility). - Compatibility configurations: Sets Windows AppCompat flags, cleans read-only attributes on game assets, configures
Options.inidefaults, and sets up GenTool and C&C:Online network launchers.
View C# implementation: ActionSet orchestrator and core fixes
public interface IActionSet
{
string Id { get; }
string Title { get; }
string Description { get; }
string Category { get; }
bool IsCoreFix { get; }
bool IsCrucialFix { get; }
Task<bool> IsApplicableAsync(GameInstallation installation, CancellationToken ct = default);
Task<bool> IsAppliedAsync(GameInstallation installation, CancellationToken ct = default);
Task<ActionSetResult> ApplyAsync(GameInstallation installation, CancellationToken ct = default);
Task<ActionSetResult> UndoAsync(GameInstallation installation, CancellationToken ct = default);
}
public class ActionSetOrchestrator(
IEnumerable<IActionSet> actionSets,
IEnumerable<IActionSetProvider> providers,
ILogger<ActionSetOrchestrator> logger) : IActionSetOrchestrator
{
private readonly IReadOnlyList<IActionSet> _actionSets = InitializeActionSets(actionSets, providers, logger);
public async Task<IReadOnlyList<IActionSet>> GetApplicableCoreFixesAsync(
GameInstallation installation,
CancellationToken ct = default)
{
var applicable = new List<IActionSet>();
foreach (var actionSet in _actionSets.Where(x => x.IsCoreFix))
{
ct.ThrowIfCancellationRequested();
if (await actionSet.IsApplicableAsync(installation, ct))
{
applicable.Add(actionSet);
}
}
return applicable;
}
public async Task<OperationResult<int>> ApplyActionSetsAsync(
GameInstallation installation,
IEnumerable<IActionSet> targetSets,
CancellationToken ct = default)
{
int appliedCount = 0;
foreach (var actionSet in targetSets)
{
var result = await actionSet.ApplyAsync(installation, ct);
if (result.Success) appliedCount++;
else if (actionSet.IsCrucialFix)
return OperationResult<int>.CreateFailure($"Critical fix failed: {result.ErrorMessage}");
}
return OperationResult<int>.CreateSuccess(appliedCount);
}
}
3. Workspace isolation and incremental delta reconciliation
When launching a mod, GenHub avoids copying 10 to 30 GB of base game files. Instead, IWorkspaceManager creates a lightweight sandbox directory in under 200ms using the Strategy pattern:
SymlinkOnly: Creates relative symbolic links / directory junctions to base game.BIGarchives and CAS blobs. Uses negligible disk space.HybridCopySymlink: Symlinks large immutable archives while making shallow physical copies of mutable configuration files (GameData.ini,Options.ini), allowing mod-specific tweaks without mutating the original installation.FullCopy&HardLink: Fallback strategies for file systems with restricted symlink privileges or strict cross-volume boundaries.
Additionally, the WorkspaceReconciler analyzes delta changes (+Add, ~Update, -Remove, =Skip) when switching mod configurations, updating only changed files instead of rebuilding entire sandboxes from scratch.
View C# implementation: Workspace reconciliation and isolation strategy
public sealed class WorkspaceReconciler(ILogger<WorkspaceReconciler> logger, IFileOperationsService fileOperations)
{
public async Task<List<WorkspaceDelta>> AnalyzeWorkspaceDeltaAsync(
WorkspaceInfo? existingWorkspace,
WorkspaceConfiguration configuration,
bool forceFullVerification = false)
{
var deltas = new List<WorkspaceDelta>();
var workspacePath = Path.Combine(configuration.WorkspaceRootPath, configuration.Id);
var expectedFiles = configuration.Manifests
.SelectMany(m => m.Files ?? [])
.Where(f => f.InstallTarget == ContentInstallTarget.Workspace)
.ToDictionary(f => f.RelativePath, StringComparer.OrdinalIgnoreCase);
// Fast path: if workspace already matches expected files, skip file operations
foreach (var (relPath, manifestFile) in expectedFiles)
{
var targetPath = Path.Combine(workspacePath, relPath);
if (!fileOperations.FileExists(targetPath))
{
deltas.Add(new WorkspaceDelta(WorkspaceDeltaOperation.Add, relPath, manifestFile));
}
else if (forceFullVerification && !await fileOperations.VerifyHashAsync(targetPath, manifestFile.Hash))
{
deltas.Add(new WorkspaceDelta(WorkspaceDeltaOperation.Update, relPath, manifestFile));
}
else
{
deltas.Add(new WorkspaceDelta(WorkspaceDeltaOperation.Skip, relPath, manifestFile));
}
}
return deltas;
}
}
public sealed class HybridCopySymlinkStrategy(IFileSystem fileSystem, ICasStorage casStorage) : IWorkspaceStrategy
{
public WorkspaceType Type => WorkspaceType.HybridCopySymlink;
public async Task<Result> ConstructWorkspaceAsync(
GameInstallation installation,
GameProfile profile,
IReadOnlyList<ContentManifest> activeManifests,
string targetSandboxDir,
CancellationToken ct)
{
fileSystem.CreateDirectory(targetSandboxDir);
// 1. Symlink immutable base game archives (.BIG)
foreach (var baseFile in fileSystem.GetFiles(installation.RootPath))
{
var fileName = Path.GetFileName(baseFile);
var destPath = Path.Combine(targetSandboxDir, fileName);
if (fileName.EndsWith(".big", StringComparison.OrdinalIgnoreCase))
{
fileSystem.CreateSymbolicLink(destPath, baseFile);
}
else
{
fileSystem.CopyFile(baseFile, destPath, overwrite: false);
}
}
// 2. Layer active mod manifest files from CAS
foreach (var manifest in activeManifests)
{
foreach (var fileEntry in manifest.Files)
{
var blobSource = casStorage.GetBlobPath(fileEntry.CasHash);
var destPath = Path.Combine(targetSandboxDir, fileEntry.RelativePath);
fileSystem.CreateDirectory(Path.GetDirectoryName(destPath)!);
fileSystem.CreateSymbolicLink(destPath, blobSource);
}
}
return Result.Success();
}
}
4. Content pipeline and community subscriber catalogs
Content ingestion operates through an extensible provider pipeline:
IContentDeliveryOrchestrator: Resolves content requests across configured providers.IContentDeliveryProvider: Implements source-specific fetchers (Community Outpost GenPatcher .dat feeds, GitHub Releases, ModDB, or local directory scanning).IContentDeliverer&IContentResolver: Verifies checksum integrity, unbundles archives, generates CAS hashes, and registers manifests intoIContentManifestPool.- Subscriber Catalogs: Enables community mod authors to publish decentralized JSON catalog files. Players can add remote catalog URLs to their client to automatically discover, update, and resolve dependencies for custom mod packages without updating the core application.
View C# implementation: Content delivery provider and catalog subscription
public interface IContentDeliveryProvider
{
string ProviderId { get; }
Task<Result<IReadOnlyList<RemoteCatalogEntry>>> FetchCatalogAsync(Uri catalogUri, CancellationToken ct);
Task<Result<Stream>> DownloadContentAsync(RemoteCatalogEntry entry, IProgress<double>? progress, CancellationToken ct);
}
public sealed class CatalogSubscriptionManager(
IHttpClientFactory clientFactory,
IContentManifestPool manifestPool,
ICasStorage casStorage) : ICatalogSubscriptionManager
{
public async Task<Result<int>> SynchronizeCatalogAsync(CatalogSubscription subscription, CancellationToken ct)
{
var client = clientFactory.CreateClient();
var response = await client.GetAsync(subscription.FeedUrl, ct);
if (!response.IsSuccessStatusCode)
{
return Result<int>.Failure($"Failed to fetch catalog feed from {subscription.FeedUrl}: HTTP {response.StatusCode}");
}
var catalogJson = await response.Content.ReadAsStringAsync(ct);
var catalog = JsonSerializer.Deserialize<CommunityCatalogDto>(catalogJson);
if (catalog is null) return Result<int>.Failure("Invalid catalog JSON schema.");
int syncedCount = 0;
foreach (var manifestDto in catalog.Manifests)
{
var manifest = ManifestFactory.CreateFromDto(manifestDto);
await manifestPool.RegisterManifestAsync(manifest, ct);
syncedCount++;
}
return Result<int>.Success(syncedCount);
}
}
5. Process orchestration, concurrency mutex, and CPU affinity
The legacy SAGE game engine encounters timing race conditions on multi-core modern CPUs. GenHub’s GameLauncher coordinates process creation with hardware affinity locking:
- Concurrency Mutex: Acquires a named OS mutex per profile to prevent launching conflicting instances of the same mod.
- CPU Affinity Mask: Locks the process to a single physical core (
Process.ProcessorAffinity = new IntPtr(1 << coreIndex)), eliminating timing crashes during multiplayer matches. - Lifecycle Cleanup: Monitors process exit events to automatically unmount workspace links and release file locks.
View C# implementation: GameLauncher with CPU affinity and concurrency mutex
public sealed class GameLauncher(
IWorkspaceManager workspaceManager,
IGameProcessManager processManager) : IGameLauncher
{
public async Task<Result<LaunchSession>> LaunchProfileAsync(
GameProfile profile,
GameInstallation installation,
CancellationToken ct = default)
{
var mutexName = $"Global\\GenHub_Profile_{profile.Id:N}";
if (!Mutex.TryOpenExisting(mutexName, out _))
{
using var mutex = new Mutex(true, mutexName, out var createdNew);
if (!createdNew) return Result<LaunchSession>.Failure("Profile is already running in another session.");
// 1. Build isolated workspace sandbox
var sandboxPath = Path.Combine(Path.GetTempPath(), "GenHub", "Sandboxes", profile.Id.ToString("N"));
var workspaceResult = await workspaceManager.PrepareSandboxAsync(profile, installation, sandboxPath, ct);
if (!workspaceResult.IsSuccess) return Result<LaunchSession>.Failure(workspaceResult.Error);
// 2. Spawn game process with single-core CPU affinity lock
var executablePath = Path.Combine(sandboxPath, profile.ClientExecutableName);
var process = processManager.StartProcess(executablePath, profile.CustomArguments);
// Lock to single physical core (Core 1: 0x02) to prevent SAGE engine timing crashes
process.ProcessorAffinity = new IntPtr(1 << profile.TargetCpuCore);
return Result<LaunchSession>.Success(new LaunchSession(process.Id, sandboxPath, profile.Id));
}
return Result<LaunchSession>.Failure("Profile execution mutex is locked.");
}
}