Exemple #1
0
        private void LoadModsFromDirectories(params string[] modDirectories)
        {
            Logger.Log(LogLevel.Info, "Scanning the \"mods\" directory...");

            var stopWatch = Stopwatch.StartNew();

            // Look for mods, load their manifests
            var allMods = modDirectories.Where(Directory.Exists).SelectMany(GetZipmodsFromDirectory);

            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.Log(LogLevel.Info, $"Skipping archive \"{GetRelativeArchiveDir(archivePath)}\" because it's meant for {manifest.Game}");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(LogLevel.Error, $"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.Log(LogLevel.Warning, $"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);
                    LoadedManifests.Add(manifest);

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

                    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.Log(LogLevel.Error, $"Failed to load archive \"{GetRelativeArchiveDir(archive.Name)}\" with error: {ex}");
                }
            }

            stopWatch.Stop();
            Logger.Log(LogLevel.Info, $"List of loaded mods:\n{modLoadInfoSb}" +
                       $"Successfully loaded {Archives.Count} mods out of {allMods.Count()} archives in {stopWatch.ElapsedMilliseconds}ms");

            UniversalAutoResolver.SetResolveInfos(_gatheredResolutionInfos);

            BuildPngOnlyFolderList();
        }
Exemple #2
0
        private void LoadAllLists(ZipFile arc, Manifest manifest)
        {
            List <ZipEntry> BoneList = new List <ZipEntry>();

            foreach (ZipEntry entry in arc)
            {
                if (entry.Name.StartsWith("abdata/list/characustom", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var stream      = arc.GetInputStream(entry);
                        var chaListData = Lists.LoadCSV(stream);

                        SetPossessNew(chaListData);
                        UniversalAutoResolver.GenerateResolutionInfo(manifest, chaListData, _gatheredResolutionInfos);
                        Lists.ExternalDataList.Add(chaListData);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError($"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                    }
                }
#if KK || AI || HS2
                else if (entry.Name.StartsWith("abdata/studio/info", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    if (Path.GetFileNameWithoutExtension(entry.Name).ToLower().StartsWith("itembonelist_"))
                    {
                        BoneList.Add(entry);
                    }
                    else
                    {
                        try
                        {
                            var stream         = arc.GetInputStream(entry);
                            var studioListData = Lists.LoadStudioCSV(stream, entry.Name);

                            UniversalAutoResolver.GenerateStudioResolutionInfo(manifest, studioListData);
                            Lists.ExternalStudioDataList.Add(studioListData);
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError($"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                        }
                    }
                }
#if AI || HS2
                else if (entry.Name.StartsWith("abdata/list/map/", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        string assetBundleName = entry.Name;
                        assetBundleName  = assetBundleName.Remove(0, assetBundleName.IndexOf('/') + 1); //Remove "abdata/"
                        assetBundleName  = assetBundleName.Remove(assetBundleName.LastIndexOf('/'));    //Remove the .csv filename
                        assetBundleName += ".unity3d";

                        string assetName = entry.Name;
                        assetName = assetName.Remove(0, assetName.LastIndexOf('/') + 1); //Remove all but the filename
                        assetName = assetName.Remove(assetName.LastIndexOf('.'));        //Remove the .csv

                        var stream = arc.GetInputStream(entry);
                        Lists.LoadExcelDataCSV(assetBundleName, assetName, stream);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError($"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                    }
                }
#endif
#endif
            }

#if KK || AI || HS2
            //ItemBoneList data must be resolved after the corresponding item so they can be resolved to the same ID
            foreach (ZipEntry entry in BoneList)
            {
                try
                {
                    var stream         = arc.GetInputStream(entry);
                    var studioListData = Lists.LoadStudioCSV(stream, entry.Name);

                    UniversalAutoResolver.GenerateStudioResolutionInfo(manifest, studioListData);
                    Lists.ExternalStudioDataList.Add(studioListData);
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                }
            }
#endif
        }
Exemple #3
0
        protected void LoadAllLists(ZipFile arc, Manifest manifest)
        {
            List <ZipEntry> BoneList = new List <ZipEntry>();

            foreach (ZipEntry entry in arc)
            {
                if (entry.Name.StartsWith("abdata/list/characustom", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var stream      = arc.GetInputStream(entry);
                        var chaListData = ListLoader.LoadCSV(stream);

                        SetPossessNew(chaListData);
                        UniversalAutoResolver.GenerateResolutionInfo(manifest, chaListData, _gatheredResolutionInfos);
                        ListLoader.ExternalDataList.Add(chaListData);
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(LogLevel.Error, $"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                    }
                }
#if KK
                else if (entry.Name.StartsWith("abdata/studio/info", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    if (Path.GetFileNameWithoutExtension(entry.Name).ToLower().StartsWith("itembonelist_"))
                    {
                        BoneList.Add(entry);
                    }
                    else
                    {
                        try
                        {
                            var stream         = arc.GetInputStream(entry);
                            var studioListData = ListLoader.LoadStudioCSV(stream, entry.Name);

                            UniversalAutoResolver.GenerateStudioResolutionInfo(manifest, studioListData);
                            ListLoader.ExternalStudioDataList.Add(studioListData);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log(LogLevel.Error, $"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                        }
                    }
                }
                else if (entry.Name.StartsWith("abdata/map/list/mapinfo/", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var     stream      = arc.GetInputStream(entry);
                        MapInfo mapListData = ListLoader.LoadMapCSV(stream);

                        ListLoader.ExternalMapList.Add(mapListData);
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(LogLevel.Error, $"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                    }
                }
#endif
            }

#if KK
            //ItemBoneList data must be resolved after the corresponding item so they can be resolved to the same ID
            foreach (ZipEntry entry in BoneList)
            {
                try
                {
                    var stream         = arc.GetInputStream(entry);
                    var studioListData = ListLoader.LoadStudioCSV(stream, entry.Name);

                    UniversalAutoResolver.GenerateStudioResolutionInfo(manifest, studioListData);
                    ListLoader.ExternalStudioDataList.Add(studioListData);
                }
                catch (Exception ex)
                {
                    Logger.Log(LogLevel.Error, $"Failed to load list file \"{entry.Name}\" from archive \"{GetRelativeArchiveDir(arc.Name)}\" with error: {ex}");
                }
            }
#endif
        }
Exemple #4
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))
                    {
                        if (!manifest.Game.IsNullOrWhiteSpace() && !GameNameList.Contains(manifest.Game.ToLower().Replace("!", "")))
                        {
                            Logger.LogInfo($"Skipping archive \"{GetRelativeArchiveDir(archivePath)}\" because it's meant for {manifest.Game}");
                        }
                        else
                        {
                            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);
                    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));
            }
        }