Skip to content

User Data Management ​

The User Data Management system provides intelligent tracking and isolation of user-generated content (maps, replays, save games) across game profiles. It ensures data integrity, prevents accidental loss, and optimizes disk usage through hard-link technology.

Overview ​

When players use Command & Conquer Generals/Zero Hour, they create various types of user data:

  • Custom Maps: Downloaded or created maps stored in My Documents\Command and Conquer Generals Zero Hour Data\Maps
  • Replays: Recorded game sessions stored in My Documents\Command and Conquer Generals Zero Hour Data\Replays
  • Save Games: Campaign progress and saved games

GenHub's User Data Management system ensures that each game profile can have its own isolated set of this content while avoiding unnecessary file duplication.

Architecture ​

Core Services ​

IProfileContentLinker ​

The primary interface for user data operations.

Location: GenHub.Core.Interfaces.UserData.IProfileContentLinker

Key Methods:

  • PrepareProfileUserDataAsync: Installs and activates user data when launching a profile
  • SwitchProfileUserDataAsync: Handles profile transitions with optional data retention
  • UpdateProfileUserDataAsync: Manages content changes within a profile
  • CleanupDeletedProfileAsync: Removes all user data when a profile is deleted
  • AnalyzeUserDataSwitchAsync: Pre-flight analysis of data impact before switching

ProfileContentLinkerService ​

The concrete implementation of user data management.

Location: GenHub.Features.UserData.Services.ProfileContentLinkerService

Responsibilities:

  • Tracks which user data files belong to which profiles
  • Creates hard links to avoid file duplication
  • Activates/deactivates user data based on active profile
  • Verifies file integrity using hash validation
  • Manages the lifecycle of user data across profile operations

IUserDataTracker ​

Low-level tracking service for user data files.

Responsibilities:

  • Maintains database of installed user data files
  • Tracks hard link relationships
  • Provides activation/deactivation primitives
  • Handles file verification and cleanup

How It Works ​

1. Profile Launch (Preparation) ​

When a profile is launched, the system:

  1. Identifies User Data Content: Scans the profile's enabled content for manifests containing user data files (maps, replays, etc.)
  2. Checks Installation Status: Queries the tracker to see if files are already installed
  3. Verifies Integrity: For existing installations, validates files using stored hashes
  4. Installs New Content: For missing or corrupted files, creates hard links from CAS to user data directories
  5. Activates Profile Data: Marks the profile's user data as active in the tracker
csharp
// Simplified flow
var result = await profileContentLinker.PrepareProfileUserDataAsync(
    profileId: "my-profile-123",
    manifests: enabledContentManifests,
    targetGame: GameType.ZeroHour,
    cancellationToken: cancellationToken
);

2. Profile Switching ​

Profile switching is the most complex operation, involving data transition and optional retention.

Without User Data Retention (Default) ​

  1. Deactivate Old Profile: Marks old profile's user data as inactive
  2. Remove Old Files: Deletes hard links for files exclusive to old profile
  3. Activate New Profile: Installs and activates new profile's user data

With User Data Retention (skipCleanup = true) ​

  1. Analyze Impact: Identifies files that would be removed
  2. User Confirmation: Displays non-blocking prompt with file count and size
  3. Adopt Files: If user chooses "Add to Profile", re-registers old profile's files with new profile
  4. Preserve Hard Links: Files remain on disk, now tracked for both profiles
csharp
// Switching with optional retention
var result = await profileContentLinker.SwitchProfileUserDataAsync(
    oldProfileId: "profile-a",
    newProfileId: "profile-b",
    newManifests: profileBManifests,
    targetGame: GameType.ZeroHour,
    skipCleanup: userChoseToKeepData,
    cancellationToken: cancellationToken
);

3. Content Updates ​

When a profile's content selection changes (adding/removing mods with maps):

  1. Compute Delta: Compares old and new manifest sets
  2. Install New Content: Adds hard links for newly enabled content
  3. Remove Deselected Content: Cleans up files from removed content
  4. Update Tracker: Synchronizes the database with new state

4. Profile Deletion ​

When a profile is deleted:

  1. Deactivate All Data: Marks all profile's user data as inactive
  2. Check References: Identifies files used exclusively by this profile
  3. Remove Exclusive Files: Deletes hard links for files not used by other profiles
  4. Preserve Shared Files: Keeps files that other profiles still reference

Hard links allow multiple directory entries to point to the same file data on disk. Benefits:

  • Zero Duplication: Same file appears in multiple locations without copying
  • Atomic Operations: File exists or doesn't exist, no partial states
  • Transparent Access: Applications see normal files, unaware of linking
  • Efficient Cleanup: Deleting one link doesn't affect others

Limitations ​

  • Same Volume Only: Hard links require source and target on same drive
  • File-Level Only: Cannot hard link directories
  • Windows/Linux Only: Platform-specific implementation

Fallback Strategy ​

If hard links fail (cross-drive scenario), the system falls back to file copying with appropriate warnings.

Data Models ​

UserDataSwitchInfo ​

Analysis results for profile switch impact.

csharp
public class UserDataSwitchInfo
{
    public string OldProfileId { get; set; }
    public string NewProfileId { get; set; }
    public int FileCount { get; set; }
    public long TotalSizeBytes { get; set; }
}

UserDataManifest ​

Tracks installed user data for a profile.

csharp
public class UserDataManifest
{
    public string ManifestId { get; set; }
    public string ProfileId { get; set; }
    public List<InstalledFile> InstalledFiles { get; set; }
    public bool IsActive { get; set; }
    public DateTime InstalledAt { get; set; }
}

InstalledFile ​

Individual file tracking with hash verification.

csharp
public class InstalledFile
{
    public string SourcePath { get; set; }
    public string TargetPath { get; set; }
    public string Hash { get; set; }
    public long SizeBytes { get; set; }
    public bool IsHardLink { get; set; }
}

User Experience ​

Switch Confirmation UI ​

When switching profiles would result in data loss, a non-blocking confirmation appears:

Visual Elements:

  • Semi-transparent overlay on profile card
  • File count and total size display
  • Two action buttons: "Add to Profile" and "Remove"
  • Large data warning (>100 files) with loading indicator

User Choices:

  • Add to Profile: Adopts old profile's user data into new profile (preserves files)
  • Remove: Proceeds with standard cleanup (removes files)
  • Cancel: Aborts the profile switch

Large Data Warning ​

When adopting >100 files, the system:

  1. Logs a warning about potential delay
  2. Displays visual indicator on confirmation overlay
  3. Shows "Loading maps..." notification during the operation

Integration Points ​

GameProfileLauncherViewModel ​

Orchestrates the user data confirmation flow:

  1. Pre-Launch Analysis: Calls AnalyzeUserDataSwitchAsync before switching
  2. UI Update: Populates GameProfileItemViewModel with switch info
  3. Pause Launch: Sets ShowUserDataConfirmation = true to display overlay
  4. Handle Response: Executes appropriate command based on user choice
  5. Resume Launch: Continues with ExecuteLaunchAsync after confirmation

ProfileLauncherFacade ​

High-level launch orchestration that delegates to IProfileContentLinker:

csharp
// Simplified launch flow
var launchResult = await gameLauncher.LaunchProfileAsync(
    profile,
    progress: null,
    skipUserDataCleanup: userChoseToKeepData,
    cancellationToken: cancellationToken
);

GameLauncher ​

Core launcher that invokes user data operations:

csharp
// During profile switch
await profileContentLinker.SwitchProfileUserDataAsync(
    oldProfileId: previousProfile,
    newProfileId: currentProfile,
    newManifests: profileManifests,
    targetGame: profile.GameClient.GameType,
    skipCleanup: skipUserDataCleanup,
    cancellationToken: cancellationToken
);

Performance Considerations ​

File Count Threshold ​

The system uses a threshold of 100 files to determine "large" transfers:

  • Below threshold: Silent operation
  • Above threshold: Warning notification and visual indicator

Database Queries ​

The tracker uses indexed queries for:

  • Profile ID lookups
  • Manifest ID lookups
  • Active status filtering
  • Hash verification

Concurrent Access ​

All operations use proper locking to prevent race conditions:

  • Profile-level locks during switches
  • File-level locks during installation
  • Database transaction isolation

Error Handling ​

Common Scenarios ​

Hard Link Failure (Cross-Drive):

  • Logs detailed error with drive information
  • Falls back to file copying
  • Notifies user of performance impact

File Verification Failure:

  • Logs hash mismatch
  • Triggers automatic reinstallation
  • Continues with other files

Insufficient Disk Space:

  • Returns failure result with clear message
  • Rolls back partial operations
  • Preserves existing state

Best Practices ​

For Developers ​

  1. Always Use Cancellation Tokens: All async operations support cancellation
  2. Check Result Success: Never assume operations succeed
  3. Log Appropriately: Use structured logging with profile/manifest IDs
  4. Handle Partial Failures: Some files may succeed while others fail

For Users ​

  1. Same Drive Recommended: Keep game installation and GenHub data on same drive for hard link support
  2. Review Large Transfers: Pay attention to warnings when adopting many files
  3. Regular Cleanup: Delete unused profiles to free up disk space
  4. Backup Important Data: While the system is robust, external backups are always recommended

Future Enhancements ​

Potential improvements under consideration:

  • Cloud Sync: Synchronize user data across machines
  • Compression: Compress inactive user data to save space
  • Deduplication: Identify and merge identical files across profiles
  • Import/Export: Package user data for sharing with other users
  • Selective Retention: Choose specific files to keep during switches

GeneralsHub Docs