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); } }
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); } }
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)); } }
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(); }
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 }