/// <summary> /// install mods as an asynchronous operation. /// </summary> /// <param name="statusToRetain">The status to retain.</param> /// <returns>Task<System.Boolean>.</returns> public virtual async Task <IReadOnlyCollection <IModInstallationResult> > InstallModsAsync(IEnumerable <IMod> statusToRetain) { var game = GameService.GetSelected(); if (game == null) { return(null); } var mods = GetInstalledModsInternal(game, false); var descriptors = new List <IModInstallationResult>(); var userDirectoryMods = GetAllModDescriptors(Path.Combine(game.UserDirectory, Shared.Constants.ModDirectory), ModSource.Local); if (userDirectoryMods?.Count() > 0) { descriptors.AddRange(userDirectoryMods); } var workshopDirectoryMods = GetAllModDescriptors(game.WorkshopDirectory, ModSource.Steam); if (workshopDirectoryMods?.Count() > 0) { descriptors.AddRange(workshopDirectoryMods); } var diffs = descriptors.Where(p => p.Mod != null && !mods.Any(m => m.DescriptorFile.Equals(p.Mod.DescriptorFile, StringComparison.OrdinalIgnoreCase))).ToList(); if (diffs.Count > 0) { var result = new List <IModInstallationResult>(); await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() { RootDirectory = game.UserDirectory, Path = Shared.Constants.ModDirectory }); var tasks = new List <Task>(); foreach (var diff in diffs.GroupBy(p => p.Mod.DescriptorFile)) { var installResult = diff.FirstOrDefault(); var localDiff = diff.FirstOrDefault().Mod; if (IsPatchModInternal(localDiff)) { continue; } tasks.Add(Task.Run(async() => { bool shouldLock = CheckIfModShouldBeLocked(game, localDiff); if (statusToRetain != null && !shouldLock) { var mod = statusToRetain.FirstOrDefault(p => p.DescriptorFile.Equals(localDiff.DescriptorFile, StringComparison.OrdinalIgnoreCase)); if (mod != null) { shouldLock = mod.IsLocked; } } await ModWriter.WriteDescriptorAsync(new ModWriterParameters() { Mod = localDiff, RootDirectory = game.UserDirectory, Path = localDiff.DescriptorFile, LockDescriptor = shouldLock }, IsPatchModInternal(localDiff));; })); installResult.Installed = true; result.Add(installResult); } if (tasks.Count > 0) { await Task.WhenAll(tasks); Cache.Invalidate(ModsCachePrefix, ConstructModsCacheKey(game, true), ConstructModsCacheKey(game, false)); } if (descriptors.Any(p => p.Invalid)) { result.AddRange(descriptors.Where(p => p.Invalid)); } return(result); } if (descriptors.Any(p => p.Invalid)) { return(descriptors.Where(p => p.Invalid).ToList()); } return(null); }
/// <summary> /// Gets the installed mods internal. /// </summary> /// <param name="game">The game.</param> /// <param name="ignorePatchMods">if set to <c>true</c> [ignore patch mods].</param> /// <returns>IEnumerable<IMod>.</returns> protected virtual IEnumerable <IMod> GetInstalledModsInternal(string game, bool ignorePatchMods) { return(GetInstalledModsInternal(GameService.Get().FirstOrDefault(p => p.Type.Equals(game)), ignorePatchMods)); }
/// <summary> /// Evals the definition priority internal. /// </summary> /// <param name="definitions">The definitions.</param> /// <returns>IPriorityDefinitionResult.</returns> protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnumerable <IDefinition> definitions) { // We're expecting properly ordered definitions based on load order. // In case of game being included this should be done by the calling method as well, // though there should not be any problems since it's all based on a list of strings modOrder.IndexOf(modName). // And the game is never a mod. If this changes this is going to be bad for me. var game = GameService.GetSelected(); var result = GetModelInstance <IPriorityDefinitionResult>(); if (game != null && definitions?.Count() > 1) { // Handle localizations differently var file = definitions.FirstOrDefault().File ?? string.Empty; if (file.StartsWith(Shared.Constants.LocalizationDirectory)) { IEnumerable <IDefinition> filtered = null; if (definitions.Any(p => p.FileCI.Contains(Shared.Constants.LocalizationReplaceDirectory, StringComparison.OrdinalIgnoreCase))) { var replaceDefinitions = definitions.Where(p => p.FileCI.Contains(Shared.Constants.LocalizationReplaceDirectory, StringComparison.OrdinalIgnoreCase)); if (replaceDefinitions.GroupBy(p => p.CustomPriorityOrder).Count() == 1) { filtered = replaceDefinitions.ToList(); } else { var topPriority = replaceDefinitions.OrderByDescending(p => p.CustomPriorityOrder).FirstOrDefault().CustomPriorityOrder; filtered = replaceDefinitions.Where(p => p.CustomPriorityOrder == topPriority); } } else { if (definitions.GroupBy(p => p.CustomPriorityOrder).Count() == 1) { filtered = definitions.ToList(); } else { var topPriority = definitions.OrderByDescending(p => p.CustomPriorityOrder).FirstOrDefault().CustomPriorityOrder; filtered = definitions.Where(p => p.CustomPriorityOrder == topPriority); } } var uniqueDefinitions = filtered.GroupBy(p => p.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.File), StringComparer.Ordinal).Last()); if (uniqueDefinitions.Count() == 1) { var definition = uniqueDefinitions.FirstOrDefault(p => !p.IsFromGame); if (definition == null) { definition = uniqueDefinitions.FirstOrDefault(); } result.Definition = definition; } else if (uniqueDefinitions.Count() > 1) { var modDefinitions = uniqueDefinitions.Where(p => !p.IsFromGame); if (!modDefinitions.Any()) { definitions = uniqueDefinitions; } result.Definition = modDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.File), StringComparer.Ordinal).Last(); } } else { var validDefinitions = definitions.Where(p => p.ExistsInLastFile).ToList(); if (validDefinitions.Count == 1) { result.Definition = validDefinitions.FirstOrDefault(); // If it's the only valid one assume load order is responsible result.PriorityType = DefinitionPriorityType.ModOrder; } else if (validDefinitions.Count > 1) { var definitionEvals = new List <DefinitionEval>(); var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type)); bool isFios = false; if (provider != null) { bool overrideSkipped = false; isFios = provider.DefinitionUsesFIOSRules(validDefinitions.First()); foreach (var item in validDefinitions) { var fileName = isFios ? item.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).First() : item.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).Last(); var hasOverrides = validDefinitions.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(d => d.Equals(item.ModName)) && (isFios ? p.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).First().Equals(fileName) : p.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).Last().Equals(fileName))); if (hasOverrides) { overrideSkipped = true; continue; } definitionEvals.Add(new DefinitionEval() { Definition = item, FileName = fileName }); } List <DefinitionEval> uniqueDefinitions; if (isFios) { uniqueDefinitions = definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).First()).ToList(); } else { uniqueDefinitions = definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).Last()).ToList(); } // Filter out game definitions which might have the same filename var filteredGameDefinitions = false; var gameDefinitions = uniqueDefinitions.GroupBy(p => p.FileNameCI).Where(p => p.Any(a => a.Definition.IsFromGame)).SelectMany(p => p.Where(w => w.Definition.IsFromGame)); if (gameDefinitions.Any()) { filteredGameDefinitions = true; foreach (var gameDef in gameDefinitions) { uniqueDefinitions.Remove(gameDef); } } if (uniqueDefinitions.Count == 1 && (overrideSkipped || filteredGameDefinitions)) { var definition = definitionEvals.FirstOrDefault(p => !p.Definition.IsFromGame); if (definition == null) { definition = definitionEvals.FirstOrDefault(); } result.Definition = definition.Definition; if (overrideSkipped) { result.PriorityType = DefinitionPriorityType.ModOverride; } else if (filteredGameDefinitions) { result.PriorityType = DefinitionPriorityType.ModOrder; } } else if (uniqueDefinitions.Count > 1) { // Has same filenames? if (uniqueDefinitions.GroupBy(p => p.FileNameCI).Count() == 1) { if (uniqueDefinitions.Any(p => p.Definition.IsCustomPatch)) { result.Definition = uniqueDefinitions.FirstOrDefault(p => p.Definition.IsCustomPatch).Definition; result.PriorityType = DefinitionPriorityType.ModOrder; } else { result.Definition = uniqueDefinitions.Last().Definition; result.PriorityType = DefinitionPriorityType.ModOrder; } } else { // Using FIOS or LIOS? if (isFios) { result.Definition = uniqueDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.FileName), StringComparer.Ordinal).First().Definition; result.PriorityType = DefinitionPriorityType.FIOS; } else { result.Definition = uniqueDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.FileName), StringComparer.Ordinal).Last().Definition; result.PriorityType = DefinitionPriorityType.LIOS; } } } } } } } if (result.Definition == null) { var definition = definitions?.FirstOrDefault(p => !p.IsFromGame); if (definition == null && (definitions?.Any()).GetValueOrDefault()) { definition = definitions.FirstOrDefault(); } result.Definition = definition; } return(result); }
/// <summary> /// install mods as an asynchronous operation. /// </summary> /// <param name="statusToRetain">The status to retain.</param> /// <returns>Task<System.Boolean>.</returns> public virtual async Task <IReadOnlyCollection <IModInstallationResult> > InstallModsAsync(IEnumerable <IMod> statusToRetain) { var game = GameService.GetSelected(); if (game == null) { return(null); } var mods = GetInstalledModsInternal(game, false); var descriptors = new List <IModInstallationResult>(); var userDirectoryMods = GetAllModDescriptors(Path.Combine(game.UserDirectory, Shared.Constants.ModDirectory), ModSource.Local); if (userDirectoryMods?.Count() > 0) { descriptors.AddRange(userDirectoryMods); } if (!string.IsNullOrWhiteSpace(game.CustomModDirectory)) { var customMods = GetAllModDescriptors(GetModDirectoryRootPath(game), ModSource.Local); if (customMods != null && customMods.Any()) { descriptors.AddRange(customMods); } } var workshopDirectoryMods = game.WorkshopDirectory.SelectMany(p => GetAllModDescriptors(p, ModSource.Steam)); if (workshopDirectoryMods.Any()) { descriptors.AddRange(workshopDirectoryMods); } var filteredDescriptors = new List <IModInstallationResult>(); var grouped = descriptors.GroupBy(p => p.ParentDirectory); foreach (var item in grouped) { if (item.Any()) { if (item.All(p => p.IsFile)) { filteredDescriptors.AddRange(item); } else { filteredDescriptors.AddRange(item.Where(p => !p.IsFile)); } } } var diffs = filteredDescriptors.Where(p => p.Mod != null && !mods.Any(m => m.DescriptorFile.Equals(p.Mod.DescriptorFile, StringComparison.OrdinalIgnoreCase) && m.Version.Equals(p.Mod.Version) && m.Name.Equals(p.Mod.Name))).ToList(); if (diffs.Count > 0) { var result = new List <IModInstallationResult>(); await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() { RootDirectory = game.UserDirectory, Path = Shared.Constants.ModDirectory }); var tasks = new List <Task>(); foreach (var diff in diffs.GroupBy(p => p.Mod.DescriptorFile)) { var installResult = diff.FirstOrDefault(); var localDiff = diff.FirstOrDefault().Mod; if (IsPatchModInternal(localDiff)) { continue; } tasks.Add(Task.Run(async() => { bool shouldLock = CheckIfModShouldBeLocked(game, localDiff); if (statusToRetain != null && !shouldLock) { var mod = statusToRetain.FirstOrDefault(p => p.DescriptorFile.Equals(localDiff.DescriptorFile, StringComparison.OrdinalIgnoreCase)); if (mod != null) { shouldLock = mod.IsLocked; } } await ModWriter.WriteDescriptorAsync(new ModWriterParameters() { Mod = localDiff, RootDirectory = game.UserDirectory, Path = localDiff.DescriptorFile, LockDescriptor = shouldLock }, IsPatchModInternal(localDiff)); })); installResult.Installed = true; result.Add(installResult); } if (tasks.Count > 0) { await Task.WhenAll(tasks); Cache.Invalidate(new CacheInvalidateParameters() { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List <string> { GetModsCacheKey(true), GetModsCacheKey(false) } }); } if (filteredDescriptors.Any(p => p.Invalid)) { result.AddRange(filteredDescriptors.Where(p => p.Invalid)); } return(result); } if (filteredDescriptors.Any(p => p.Invalid)) { return(filteredDescriptors.Where(p => p.Invalid).ToList()); } return(null); }