예제 #1
0
        public Sideloader()
        {
            //ilmerge
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
            {
                if (args.Name == "I18N, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" ||
                    args.Name == "I18N.West, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
                {
                    return(Assembly.GetExecutingAssembly());
                }

                return(null);
            };

            //install hooks
            Hooks.InstallHooks();
            AutoResolver.Hooks.InstallHooks();
            ResourceRedirector.ResourceRedirector.AssetResolvers.Add(RedirectHook);

            //check mods directory
            string modDirectory = Path.Combine(Paths.GameRootPath, "mods");

            if (!Directory.Exists(modDirectory))
            {
                return;
            }


            //load zips
            foreach (string archivePath in Directory.GetFiles(modDirectory, "*.zip", SearchOption.AllDirectories))
            {
                var archive = new ZipFile(archivePath);

                if (!Manifest.TryLoadFromZip(archive, out Manifest manifest))
                {
                    Logger.Log(LogLevel.Warning, $"[SIDELOADER] Cannot load {Path.GetFileName(archivePath)} due to missing/invalid manifest.");
                    continue;
                }

                if (LoadedManifests.Any(x => x.GUID == manifest.GUID))
                {
                    Logger.Log(LogLevel.Warning, $"[SIDELOADER] Skipping {Path.GetFileName(archivePath)} due to duplicate GUID \"{manifest.GUID}\".");
                    continue;
                }

                var manifestName = !string.IsNullOrEmpty(manifest.Name?.Trim())
                    ? manifest.Name
                    : Path.GetFileName(archivePath);

                Logger.Log(LogLevel.Info, $"[SIDELOADER] Loaded {manifestName} {manifest.Version ?? ""}");

                Archives.Add(archive);
                LoadedManifests.Add(manifest);

                LoadAllUnityArchives(archive);

                LoadAllLists(archive, manifest);
            }
        }
예제 #2
0
        public Sideloader()
        {
            //ilmerge
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
            {
                if (args.Name == "I18N, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" ||
                    args.Name == "I18N.West, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
                {
                    return(Assembly.GetExecutingAssembly());
                }

                return(null);
            };

            //install hooks
            Hooks.InstallHooks();
            AutoResolver.Hooks.InstallHooks();
            ResourceRedirector.ResourceRedirector.AssetResolvers.Add(RedirectHook);

            //check mods directory
            string modDirectory = Path.Combine(Utility.ExecutingDirectory, "mods");

            if (!Directory.Exists(modDirectory))
            {
                return;
            }


            //load zips
            foreach (string archivePath in Directory.GetFiles(modDirectory, "*.zip"))
            {
                var archive = new ZipFile(archivePath);

                if (!Manifest.TryLoadFromZip(archive, out Manifest manifest))
                {
                    BepInLogger.Log($"[SIDELOADER] Cannot load {Path.GetFileName(archivePath)} due to missing/invalid manifest.");
                    continue;
                }

                string name = manifest.Name ?? Path.GetFileName(archivePath);
                BepInLogger.Log($"[SIDELOADER] Loaded {name} {manifest.Version ?? ""}");

                Archives.Add(archive);

                LoadAllUnityArchives(archive);

                LoadAllLists(archive, manifest);
            }
        }
예제 #3
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));
            }
        }
예제 #4
0
        private void LoadModsFromDirectory(string modDirectory)
        {
            string GetRelativeArchiveDir(string archiveDir)
            {
                return(archiveDir.Length < modDirectory.Length ? archiveDir : archiveDir.Substring(modDirectory.Length).Trim(' ', '/', '\\'));
            }

            Logger.Log(LogLevel.Info, "[SIDELOADER] Scanning the \"mods\" directory...");

            // Look for mods, load their manifests
            var allMods = Directory.GetFiles(modDirectory, "*", SearchOption.AllDirectories)
                          .Where(x => x.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) ||
                                 x.EndsWith(".zipmod", StringComparison.OrdinalIgnoreCase));

            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))
                    {
                        archives.Add(archive, manifest);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(LogLevel.Error, $"[SIDELOADER] Failed to load archive \"{GetRelativeArchiveDir(archivePath)}\" with error: {ex.Message}");
                    Logger.Log(LogLevel.Debug, $"[SIDELOADER] Error details: {ex}");
                    archive?.Close();
                }
            }

            // Handlie 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, $"[SIDELOADER] Archives with identical GUIDs detected! Archives: {modList}");
                    Logger.Log(LogLevel.Warning, $"[SIDELOADER] 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);

                    Logger.Log(LogLevel.Info, $"[SIDELOADER] Loaded {displayName} {manifest.Version ?? ""}");
                }
                catch (Exception ex)
                {
                    Logger.Log(LogLevel.Error, $"[SIDELOADER] Failed to load archive \"{GetRelativeArchiveDir(archive.Name)}\" with error: {ex.Message}");
                    Logger.Log(LogLevel.Debug, $"[SIDELOADER] Error details: {ex}");
                }
            }
            BuildPngOnlyFolderList();
        }
예제 #5
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
        }