The Game Installations feature provides automatic discovery of Command & Conquer: Generals and Zero Hour installations across both Windows and Linux platforms.
It follows a layered architecture to ensure platform abstraction, consistent results, and developer‑friendly APIs.
Game Directory Constants
When referencing standard game installation directories, use the constants provided in GameClientConstants:
GameClientConstants.GeneralsDirectoryName→"Command and Conquer Generals"GameClientConstants.ZeroHourDirectoryName→"Command and Conquer Generals Zero Hour"
This ensures consistency across all installation detectors and makes future updates easier.
Architecture Layers
1. GameInstallationService
The main service layer that exposes the public API and handles in‑memory caching.
- Caching:
Results are detected once and cached using a thread‑safeSemaphoreSlim. - Error Handling:
Validates input parameters and reports descriptive error messages to API consumers. - Lazy Loading:
Detection occurs only on the first request, then cached for reuse.
2. GameInstallationDetectionOrchestrator
Coordinates all registered platform detectors and aggregates their results.
- Platform Filtering: Runs only detectors compatible with the current operating system.
- Result Aggregation: Combines results into a single
DetectionResult. - Error Collection: Accumulates detector errors without stopping the entire process.
- Performance Monitoring: Tracks elapsed detection time per run.
3. Platform Detectors
Platform‑specific modules that actually scan for game installations.
WindowsInstallationDetector
Steam Detection
- Uses registry keys (
SteamPath/InstallPath) - Parses
libraryfolders.vdfto locate all installed Steam libraries - Scans
steamapps/commonfor Generals & Zero Hour
- Uses registry keys (
EA App Detection
- Checks registry under
SOFTWARE\Electronic Arts\EA Desktop - Reads
InstallSuccessfulandInstallLocation - Checks
SOFTWARE\WOW6432Node\EA Games\Command and Conquer Generals Zero Hourfor installation paths
- Checks registry under
LinuxInstallationDetector
Steam Detection (Proton)
- Reads
libraryfolders.vdfin common paths:~/.local/share/Steam/steamapps/libraryfolders.vdf~/.steam/steam/steamapps/libraryfolders.vdf/usr/share/steam/steamapps/libraryfolders.vdf~/.var/app/com.valvesoftware.Steam/.local/share/Steam~/snap/steam/common/.local/share/Steam
- For each library, checks under
steamapps/common/ - Can detect different packaging types (binary, flatpak, snap)
- Reads
Lutris (EA App)
- Uses Lutris cli
- Can detect different packaging types (binary, flatpak, snap soon)
Wine/Proton Prefix Detection
- Searches known Wine prefix locations:
~/.wine~/.local/share/wineprefixes~/.PlayOnLinux/wineprefix~/.var/app/com.usebottles.bottles/data/bottles/bottles/opt/wine
- Validates prefix (
drive_c/windows/system32must exist) - Looks for game directories under
Program Files*/EA GamesandProgram Files*/Command and Conquer - Confirms via
generals.exepresence
- Searches known Wine prefix locations:
Models and Results
The system returns structured, layered results at each step.
OperationResult<T>
Used by API/service layer operations.
public class OperationResult<T>
{
public bool Success { get; }
public T? Data { get; }
public IReadOnlyList<string> Errors { get; }
public static OperationResult<T> CreateSuccess(T data);
public static OperationResult<T> CreateFailure(string error);
public static OperationResult<T> CreateFailure(IEnumerable<string> errors);
}Example usage:
GetInstallationAsync(string id)→ returnsOperationResult<GameInstallation>GetAllInstallationsAsync()→ returnsOperationResult<IReadOnlyList<GameInstallation>>
DetectionResult<T>
Used by detection layer (per detector).
public sealed class DetectionResult<T>
{
public bool Success { get; }
public IReadOnlyList<T> Items { get; }
public IReadOnlyList<string> Errors { get; }
public TimeSpan Elapsed { get; }
public static DetectionResult<T> CreateSuccess(IEnumerable<T> items, TimeSpan elapsed);
public static DetectionResult<T> CreateFailure(string error);
public static DetectionResult<T> CreateFailure(IEnumerable<string> errors);
}Example usage:
WindowsInstallationDetector.DetectInstallationsAsync()
→ returnsDetectionResult<GameInstallation>containing 0–N installations
Error Handling Strategy
Each layer has clear responsibility:
Detection Layer
- Catches registry/file system exceptions.
- Converts into
DetectionResultfailures (with errors, not crashes).
Orchestration Layer
- Runs all detectors that match the platform.
- Aggregates results.
- Collects errors without stopping detection.
Service Layer
- Caches results.
- Validates inputs.
- Exposes clean, structured
OperationResultfor API/consumer use.
Consumer Layer
- Always receives structured success with valid installations or structured failure with descriptive errors.
Result Flow
Platform Detectors → DetectionResult<T>
↓
Orchestrator → Aggregated DetectionResult<T>
↓
Service Layer → OperationResult<T>
↓
API Consumers → Structured Success/Failure with detailsSupported Platform Matrix
| Platform | Steam | EA App | Wine / Proton |
|---|---|---|---|
| Windows | ✅ Registry + vdf | ✅ Registry keys | ❌ (not applicable) |
| Linux | ✅ vdf scan | ❌ (not available) | ✅ Prefix scanning |
Example Usage
var service = new GameInstallationService(orchestrator);
// Fetch all installations
var result = await service.GetAllInstallationsAsync();
if (result.Success)
{
foreach (var install in result.Data!)
{
Console.WriteLine($"{install.InstallationType} → {install.InstallationPath}");
}
}
else
{
Console.WriteLine("Errors: " + string.Join(", ", result.Errors));
}Summary
- Cross‑platform detection: Windows (Steam, EA App) + Linux (Steam Proton, Wine)
- Layered architecture ensures separation of concerns
- Structured results with robust error handling and caching
- Extensible design: new detectors (e.g. Origin, CD installs) can be added without changing service API
