예제 #1
0
        private void LoadModsFromDirectories(params string[] modDirectories)
        {
            var stopWatch = Stopwatch.StartNew();

            // Look for mods, load their manifests
            var allMods = new List <string>();

            foreach (var modDirectory in modDirectories)
            {
                if (!modDirectory.IsNullOrWhiteSpace() && Directory.Exists(modDirectory))
                {
                    var prevCount = allMods.Count;

                    allMods.AddRange(Directory.GetFiles(modDirectory, "*", SearchOption.AllDirectories)
                                     .Where(x => x.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) ||
                                            x.EndsWith(".zipmod", StringComparison.OrdinalIgnoreCase)));

                    Logger.LogInfo("Found " + (allMods.Count - prevCount) + " zipmods in directory: " + modDirectory);
                }
            }

            var archives = allMods.RunParallel(archivePath =>
            {
                ZipFile archive = null;
                try
                {
                    archive = new ZipFile(archivePath);

                    if (Manifest.TryLoadFromZip(archive, out Manifest manifest))
                    {
                        //Skip the mod if it is not for this game
                        if (!manifest.Game.IsNullOrWhiteSpace() && !GameNameList.Contains(manifest.Game.ToLower().Replace("!", "")))
                        {
                            Logger.LogInfo($"Skipping archive \"{GetRelativeArchiveDir(archivePath)}\" because it's meant for {manifest.Game}");
                            return(null);
                        }

                        return(new { archive, manifest });
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Failed to load archive \"{GetRelativeArchiveDir(archivePath)}\" with error: {ex}");
                    archive?.Close();
                }
                return(null);
            }, 3).Where(x => x != null).ToList();

            var enableModLoadingLogging = DebugLoggingModLoading.Value;
            var modLoadInfoSb           = enableModLoadingLogging ? new StringBuilder(1000) : null;

            // Handle duplicate GUIDs and load unique mods
            foreach (var modGroup in archives.GroupBy(x => x.manifest.GUID).OrderBy(x => x.Key))
            {
                // Order by version if available, else use modified dates (less reliable)
                // If versions match, prefer mods inside folders or with more descriptive names so modpacks are preferred
                var orderedModsQuery = modGroup.All(x => !string.IsNullOrEmpty(x.manifest.Version))
                    ? modGroup.OrderByDescending(x => x.manifest.Version, new ManifestVersionComparer()).ThenByDescending(x => x.archive.Name.Length)
                    : modGroup.OrderByDescending(x => File.GetLastWriteTime(x.archive.Name));

                var orderedMods = orderedModsQuery.ToList();

                if (orderedMods.Count > 1)
                {
                    var modList = string.Join(", ", orderedMods.Skip(1).Select(x => '"' + GetRelativeArchiveDir(x.archive.Name) + '"').ToArray());
                    Logger.LogWarning($"Multiple versions detected, only \"{GetRelativeArchiveDir(orderedMods[0].archive.Name)}\" will be loaded. Skipped versions: {modList}");

                    // Don't keep the duplicate archives in memory
                    foreach (var dupeMod in orderedMods.Skip(1))
                    {
                        dupeMod.archive.Close();
                    }
                }

                // Actually load the mods (only one per GUID, the newest one)
                var archive  = orderedMods[0].archive;
                var manifest = orderedMods[0].manifest;
                try
                {
                    Archives.Add(archive);
                    ZipArchives[manifest.GUID] = archive.Name;
                    Manifests[manifest.GUID]   = manifest;

                    LoadAllUnityArchives(archive, archive.Name);
                    LoadAllLists(archive, manifest);
                    BuildPngFolderList(archive);

                    UniversalAutoResolver.GenerateMigrationInfo(manifest, _gatheredMigrationInfos);
#if AI || HS2
                    UniversalAutoResolver.GenerateHeadPresetInfo(manifest, _gatheredHeadPresetInfos);
                    UniversalAutoResolver.GenerateFaceSkinInfo(manifest, _gatheredFaceSkinInfos);
#endif

                    var trimmedName = manifest.Name?.Trim();
                    var displayName = !string.IsNullOrEmpty(trimmedName) ? trimmedName : Path.GetFileName(archive.Name);

                    if (enableModLoadingLogging)
                    {
                        modLoadInfoSb.AppendLine($"Loaded {displayName} {manifest.Version}");
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Failed to load archive \"{GetRelativeArchiveDir(archive.Name)}\" with error: {ex}");
                }
            }

            UniversalAutoResolver.SetResolveInfos(_gatheredResolutionInfos);
            UniversalAutoResolver.SetMigrationInfos(_gatheredMigrationInfos);
#if AI || HS2
            UniversalAutoResolver.SetHeadPresetInfos(_gatheredHeadPresetInfos);
            UniversalAutoResolver.SetFaceSkinInfos(_gatheredFaceSkinInfos);
            UniversalAutoResolver.ResolveFaceSkins();
#endif

            BuildPngOnlyFolderList();

#pragma warning disable CS0618 // Type or member is obsolete
            LoadedManifests = Manifests.Values.AsEnumerable().ToList();
#pragma warning restore CS0618 // Type or member is obsolete

            stopWatch.Stop();

            if (enableModLoadingLogging)
            {
                Logger.LogInfo($"List of loaded mods:\n{modLoadInfoSb}");
            }
            Logger.LogInfo($"Successfully loaded {Archives.Count} mods out of {allMods.Count} archives in {stopWatch.ElapsedMilliseconds}ms");

            var failedPaths   = allMods.Except(Archives.Select(x => x.Name));
            var failedStrings = failedPaths.Select(GetRelativeArchiveDir).ToArray();
            if (failedStrings.Length > 0)
            {
                Logger.LogWarning("Could not load " + failedStrings.Length + " mods, see previous warnings for more information. File names of skipped archives:\n" + string.Join(" | ", failedStrings));
            }
        }
예제 #2
0
        private void LoadModsFromDirectories(params string[] modDirectories)
        {
            Logger.LogInfo("Scanning the \"mods\" directory...");

            var stopWatch = Stopwatch.StartNew();

            // Look for mods, load their manifests
            var allMods = new List <string>();

            foreach (var modDirectory in modDirectories)
            {
                if (!modDirectory.IsNullOrWhiteSpace() && Directory.Exists(modDirectory))
                {
                    allMods.AddRange(GetZipmodsFromDirectory(modDirectory));
                }
            }

            var archives = new Dictionary <ZipFile, Manifest>();

            foreach (var archivePath in allMods)
            {
                ZipFile archive = null;
                try
                {
                    archive = new ZipFile(archivePath);

                    if (Manifest.TryLoadFromZip(archive, out Manifest manifest))
                    {
                        if (manifest.Game.IsNullOrWhiteSpace() || GameNameList.Contains(manifest.Game.ToLower().Replace("!", "")))
                        {
                            archives.Add(archive, manifest);
                        }
                        else
                        {
                            Logger.LogInfo($"Skipping archive \"{GetRelativeArchiveDir(archivePath)}\" because it's meant for {manifest.Game}");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Failed to load archive \"{GetRelativeArchiveDir(archivePath)}\" with error: {ex}");
                    archive?.Close();
                }
            }

            var modLoadInfoSb = new StringBuilder();

            // Handle duplicate GUIDs and load unique mods
            foreach (var modGroup in archives.GroupBy(x => x.Value.GUID))
            {
                // Order by version if available, else use modified dates (less reliable)
                // If versions match, prefer mods inside folders or with more descriptive names so modpacks are preferred
                var orderedModsQuery = modGroup.All(x => !string.IsNullOrEmpty(x.Value.Version))
                    ? modGroup.OrderByDescending(x => x.Value.Version, new ManifestVersionComparer()).ThenByDescending(x => x.Key.Name.Length)
                    : modGroup.OrderByDescending(x => File.GetLastWriteTime(x.Key.Name));

                var orderedMods = orderedModsQuery.ToList();

                if (orderedMods.Count > 1)
                {
                    var modList = string.Join(", ", orderedMods.Select(x => '"' + GetRelativeArchiveDir(x.Key.Name) + '"').ToArray());
                    Logger.LogWarning($"Archives with identical GUIDs detected! Archives: {modList}; Only \"{GetRelativeArchiveDir(orderedMods[0].Key.Name)}\" will be loaded because it's the newest");

                    // Don't keep the duplicate archives in memory
                    foreach (var dupeMod in orderedMods.Skip(1))
                    {
                        dupeMod.Key.Close();
                    }
                }

                // Actually load the mods (only one per GUID, the newest one)
                var archive  = orderedMods[0].Key;
                var manifest = orderedMods[0].Value;
                try
                {
                    Archives.Add(archive);
                    Manifests[manifest.GUID] = manifest;

                    LoadAllUnityArchives(archive, archive.Name);
                    LoadAllLists(archive, manifest);
                    BuildPngFolderList(archive);

                    UniversalAutoResolver.GenerateMigrationInfo(manifest, _gatheredMigrationInfos);

                    var trimmedName = manifest.Name?.Trim();
                    var displayName = !string.IsNullOrEmpty(trimmedName) ? trimmedName : Path.GetFileName(archive.Name);

                    modLoadInfoSb.AppendLine($"Loaded {displayName} {manifest.Version}");
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Failed to load archive \"{GetRelativeArchiveDir(archive.Name)}\" with error: {ex}");
                }
            }

            stopWatch.Stop();
            if (ModLoadingLogging.Value)
            {
                Logger.LogInfo($"List of loaded mods:\n{modLoadInfoSb}");
            }
            Logger.LogInfo($"Successfully loaded {Archives.Count} mods out of {allMods.Count()} archives in {stopWatch.ElapsedMilliseconds}ms");

            UniversalAutoResolver.SetResolveInfos(_gatheredResolutionInfos);
            UniversalAutoResolver.SetMigrationInfos(_gatheredMigrationInfos);

            BuildPngOnlyFolderList();

#pragma warning disable CS0618 // Type or member is obsolete
            LoadedManifests = Manifests.Values.AsEnumerable().ToList();
#pragma warning restore CS0618 // Type or member is obsolete
        }