Instruction file imported from Broo37/EDF6ModLoaderWpf (
.github/instructions/service-layer.instructions.md). Copyright stays with the author.
Service Layer Conventions
File Path Handling
Always use case-insensitive comparison for file paths — Windows filesystem is case-insensitive:
new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Exclude mod_info.json when enumerating mod files:
.Where(f => !f.EndsWith("mod_info.json", StringComparison.OrdinalIgnoreCase))
BuildWinnerMap Contract
The winner map determines which mod's file gets deployed. It must:
- Filter to
IsActivemods only - Iterate in ascending
LoadOrderorder (lowest first) - Use last-writer-wins — higher LoadOrder overwrites earlier entries
- Return
Dictionary<string, ModEntry>keyed on relative file path
Apply Workflow
Any operation that changes mod state must follow this sequence:
BuildWinnerMap → CleanManagedFiles → CopyWinners → SaveRegistry → UpdateStatuses
Never skip steps or reorder them.
File Operation Retry
Use retry logic for copy/delete operations to handle transient file locks:
- 5 attempts maximum
- 200ms exponential backoff:
delay = 200 * (attempt + 1) - Catch
IOExceptionandUnauthorizedAccessExceptionper attempt - Rethrow on final attempt failure
Service Design
- Services are registered as Singletons — do not store per-request state in fields
- Use
staticfor pure utility methods that don't need instance state (e.g.,GetModRelativeFiles,BuildWinnerMap) - Accept dependencies via constructor injection
- JSON persistence: use
System.Text.JsonwithWriteIndented = true - Always
Directory.CreateDirectory(destDir)before writing files
Standard Subfolders
The valid mod subfolders in [GameRoot]\Mods\ are:
DEFAULTPACKAGE, MainScript, Mission, Object, Patches, Plugins, Weapon
Reference via FileService.StandardSubfolders — don't hardcode elsewhere.