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 profileSwitchProfileUserDataAsync: Handles profile transitions with optional data retentionUpdateProfileUserDataAsync: Manages content changes within a profileCleanupDeletedProfileAsync: Removes all user data when a profile is deletedAnalyzeUserDataSwitchAsync: 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:
- Identifies User Data Content: Scans the profile's enabled content for manifests containing user data files (maps, replays, etc.)
- Checks Installation Status: Queries the tracker to see if files are already installed
- Verifies Integrity: For existing installations, validates files using stored hashes
- Installs New Content: For missing or corrupted files, creates hard links from CAS to user data directories
- Activates Profile Data: Marks the profile's user data as active in the tracker
// 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)
- Deactivate Old Profile: Marks old profile's user data as inactive
- Remove Old Files: Deletes hard links for files exclusive to old profile
- Activate New Profile: Installs and activates new profile's user data
With User Data Retention (skipCleanup = true)
- Analyze Impact: Identifies files that would be removed
- User Confirmation: Displays non-blocking prompt with file count and size
- Adopt Files: If user chooses "Add to Profile", re-registers old profile's files with new profile
- Preserve Hard Links: Files remain on disk, now tracked for both profiles
// 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):
- Compute Delta: Compares old and new manifest sets
- Install New Content: Adds hard links for newly enabled content
- Remove Deselected Content: Cleans up files from removed content
- Update Tracker: Synchronizes the database with new state
4. Profile Deletion
When a profile is deleted:
- Deactivate All Data: Marks all profile's user data as inactive
- Check References: Identifies files used exclusively by this profile
- Remove Exclusive Files: Deletes hard links for files not used by other profiles
- Preserve Shared Files: Keeps files that other profiles still reference
Hard Link Technology
Why Hard Links?
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.
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.
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.
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:
- Logs a warning about potential delay
- Displays visual indicator on confirmation overlay
- Shows "Loading maps..." notification during the operation
Integration Points
GameProfileLauncherViewModel
Orchestrates the user data confirmation flow:
- Pre-Launch Analysis: Calls
AnalyzeUserDataSwitchAsyncbefore switching - UI Update: Populates
GameProfileItemViewModelwith switch info - Pause Launch: Sets
ShowUserDataConfirmation = trueto display overlay - Handle Response: Executes appropriate command based on user choice
- Resume Launch: Continues with
ExecuteLaunchAsyncafter confirmation
ProfileLauncherFacade
High-level launch orchestration that delegates to IProfileContentLinker:
// Simplified launch flow
var launchResult = await gameLauncher.LaunchProfileAsync(
profile,
progress: null,
skipUserDataCleanup: userChoseToKeepData,
cancellationToken: cancellationToken
);GameLauncher
Core launcher that invokes user data operations:
// 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
- Always Use Cancellation Tokens: All async operations support cancellation
- Check Result Success: Never assume operations succeed
- Log Appropriately: Use structured logging with profile/manifest IDs
- Handle Partial Failures: Some files may succeed while others fail
For Users
- Same Drive Recommended: Keep game installation and GenHub data on same drive for hard link support
- Review Large Transfers: Pay attention to warnings when adopting many files
- Regular Cleanup: Delete unused profiles to free up disk space
- 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
