using System.IO; using CompFile = PCL.ModComp.CompFile; using CompFileStatus = PCL.ModComp.CompFileStatus; using CompLoaderType = PCL.ModComp.CompLoaderType; using CompProject = PCL.ModComp.CompProject; using LocalCompFile = PCL.ModLocalComp.LocalCompFile; using PCL.Core.Minecraft.ResourceProject; using PCL.Core.App.Localization; using PCL.Network; namespace PCL; public static class ModCompDependency { public static ModDependencyRequest BuildRequest( CompFile file, CompProject project, string targetMinecraftVersion, List targetLoaders, string targetModsFolder) { ArgumentNullException.ThrowIfNull(file); ArgumentNullException.ThrowIfNull(project); targetLoaders ??= new List(); var source = GetSource(project.FromCurseForge); var dependencies = file.Dependencies .Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId)) .Select(dependencyId => new ModDependencyReference { ProjectId = dependencyId, Source = source, IsRequired = true, }) .Concat(file.OptionalDependencies .Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId)) .Select(dependencyId => new ModDependencyReference { ProjectId = dependencyId, Source = source, IsRequired = false, })) .ToList(); return new ModDependencyRequest { TargetMinecraftVersion = targetMinecraftVersion ?? string.Empty, TargetLoaders = ToLoaderNames(targetLoaders), RequiredDependencies = dependencies, InstalledMods = ScanInstalledMods(targetModsFolder), ProjectResolver = ResolveProjectFiles, }; } public static List ScanInstalledMods(string targetModsFolder) { var result = new List(); if (string.IsNullOrWhiteSpace(targetModsFolder) || !Directory.Exists(targetModsFolder)) { return result; } foreach (var path in Directory.GetFiles(targetModsFolder)) { if (!LocalCompFile.IsModFile(path)) { continue; } var localFile = new LocalCompFile(path); localFile.Load(); var source = localFile.Comp is null ? null : GetSource(localFile.Comp.FromCurseForge); var gameVersions = localFile.compFile?.GameVersions?.Where(static version => !string.IsNullOrWhiteSpace(version)).ToList() ?? new List(); var loaders = ToLoaderNames(localFile.compFile?.ModLoaders); if (!string.IsNullOrWhiteSpace(localFile.Comp?.Id) && !string.IsNullOrWhiteSpace(source)) { result.Add(new InstalledModIdentity { SourceProjectId = localFile.Comp.Id, Source = source, ModId = localFile.ModId, GameVersions = gameVersions, Loaders = loaders, }); continue; } if (!string.IsNullOrWhiteSpace(localFile.compFile?.ProjectId)) { var fileSource = GetSource(localFile.compFile.FromCurseForge); result.Add(new InstalledModIdentity { SourceProjectId = localFile.compFile.ProjectId, Source = fileSource, ModId = localFile.ModId, GameVersions = gameVersions, Loaders = loaders, }); continue; } result.Add(new InstalledModIdentity { SourceProjectId = null, Source = null, ModId = localFile.ModId, GameVersions = gameVersions, Loaders = loaders, }); } return result; } public static ModDependencyProject? ResolveProjectFiles(string source, string projectId) { if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(projectId)) { return null; } var fromCurseForge = string.Equals(source, "CurseForge", StringComparison.OrdinalIgnoreCase); var files = ModComp.CompFilesGet(projectId, fromCurseForge); if (!ModComp.compProjectCache.TryGetValue(projectId, out var compProject)) { return null; } if (compProject.FromCurseForge != fromCurseForge) { return null; } return new ModDependencyProject { ProjectId = compProject.Id, Source = source, ProjectName = compProject.TranslatedName ?? compProject.RawName, Files = files.Select(compFile => new ModDependencyFile { Id = compFile.Id, DisplayName = compFile.DisplayName, Version = compFile.Version, GameVersions = compFile.GameVersions?.Where(static version => !string.IsNullOrWhiteSpace(version)).ToList() ?? new List(), Loaders = ToLoaderNames(compFile.ModLoaders), ReleaseType = MapReleaseType(compFile.Status), ReleaseDate = compFile.ReleaseDate, RequiredDependencies = compFile.Dependencies .Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId)) .Select(dependencyId => new ModDependencyReference { ProjectId = dependencyId, Source = source, IsRequired = true, }) .ToList(), }).ToList(), }; } public static ModDependencyFile? SelectCompatibleDependencyFile( ModDependencyResolutionResult result, string projectId, string source) { ArgumentNullException.ThrowIfNull(result); return result.ToInstall .FirstOrDefault(install => string.Equals(install.ProjectId, projectId, StringComparison.OrdinalIgnoreCase) && string.Equals(install.Source, source, StringComparison.OrdinalIgnoreCase)) ?.File; } public static List<(string Filename, DownloadFile File)> BuildDependencyDownloads( ModDependencyResolutionResult result, string targetModsFolder) { ArgumentNullException.ThrowIfNull(result); var downloads = new List<(string, DownloadFile)>(); foreach (var install in result.ToInstall.AsEnumerable().Reverse()) { if (!ModComp.compProjectCache.TryGetValue(install.ProjectId, out var depProject)) { continue; } var fromCurseForge = string.Equals(install.Source, "CurseForge", StringComparison.OrdinalIgnoreCase); if (depProject.FromCurseForge != fromCurseForge) { continue; } var depCompFile = ModComp.CompFilesGet(install.ProjectId, fromCurseForge) .FirstOrDefault(file => string.Equals(file.Id, install.File.Id, StringComparison.OrdinalIgnoreCase)); if (depCompFile is null) { continue; } var depFileName = ModComp.CompFileNameGet(depProject, depCompFile); var targetPath = Path.Combine(targetModsFolder ?? string.Empty, depFileName); downloads.Add((depFileName, depCompFile.ToNetFile(targetPath, ModComp.DownloadReason.Dependency))); } return downloads; } /// /// Shows confirmation dialog for required dependency installs. /// Returns: CompDepsInstallTypes.WithDeps if user chooses to install with deps, /// CompDepsInstallTypes.WithoutDeps if user chooses to install without deps, /// CompDepsInstallTypes.Cancel if user cancels, /// CompDepsInstallTypes.Unresolved if there are unresolved required deps. /// public static ModComp.CompDepsInstallTypes ConfirmDependencyInstall(ModDependencyResolutionResult result) { ArgumentNullException.ThrowIfNull(result); if (result.Unresolved is { Count: > 0 }) { ModBase.Log($"[CompDeps] 无法解析: {result.Unresolved.Count} 个必需前置"); var dependencies = string.Join( Environment.NewLine, result.Unresolved.Select(dep => Lang.Text( "Download.Comp.Dependency.Unresolved.ListItem", dep.Source, dep.ProjectId, dep.Reason))); var message = Lang.Text("Download.Comp.Dependency.Unresolved.Message", dependencies); var selectedButton = ModMain.MyMsgBox( message, Lang.Text("Download.Comp.Dependency.Unresolved.Title"), Lang.Text("Download.Comp.Dependency.Unresolved.ContinueWithoutDependencies"), Lang.Text("Common.Action.Cancel"), isWarn: true, forceWait: true); return selectedButton == 1 ? ModComp.CompDepsInstallTypes.Unresolved : ModComp.CompDepsInstallTypes.Cancel; } if (result.ToInstall is { Count: > 0 }) { var dependencies = string.Join( Environment.NewLine, result.ToInstall.Select(install => Lang.Text( "Download.Comp.Dependency.Install.ListItem", install.ProjectName, install.Source, install.File.DisplayName, install.File.Version))); var message = Lang.Text("Download.Comp.Dependency.Install.Message", dependencies); var dialogResult = ModMain.MyMsgBox( message, Lang.Text("Download.Comp.Dependency.Install.Title"), Lang.Text("Download.Comp.Dependency.Install.WithDependencies"), Lang.Text("Download.Comp.Dependency.Install.WithoutDependencies"), Lang.Text("Download.Comp.Dependency.Install.Cancel"), forceWait: true); return dialogResult switch { 1 => ModComp.CompDepsInstallTypes.WithDeps, 2 => ModComp.CompDepsInstallTypes.WithoutDeps, 3 => ModComp.CompDepsInstallTypes.Cancel, _ => ModComp.CompDepsInstallTypes.Cancel }; } return ModComp.CompDepsInstallTypes.WithDeps; } /// /// Shows abort message when dependency resolution was cancelled by user or failed. /// public static void ShowDependencyAbortMessage(string reason) { ModMain.MyMsgBox( Lang.Text("Download.Comp.Dependency.Abort.Message", reason), Lang.Text("Download.Comp.Dependency.Abort.Title"), Lang.Text("Common.Action.Confirm"), isWarn: false, forceWait: true); } private static string GetSource(bool fromCurseForge) { return fromCurseForge ? "CurseForge" : "Modrinth"; } private static List ToLoaderNames(IEnumerable? loaders) { if (loaders is null) { return new List(); } return loaders .Where(static loader => loader != CompLoaderType.Any) .Select(static loader => loader.ToString()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } private static int MapReleaseType(CompFileStatus status) { return status switch { CompFileStatus.Release => 1, CompFileStatus.Beta => 2, CompFileStatus.Alpha => 3, _ => 1, }; } }