public override void Begin() { base.Begin(); // add on-screen elements like GameLoader/OverworldLoader Add(new HudRenderer()); Add(snow); RendererList.UpdateLists(); // register the routine Entity entity = new Entity(); entity.Add(new Coroutine(Routine())); Add(entity); // run the update check task asynchronously new Task(() => { // display "checking for updates" message, in case the async task is not done yet. modUpdatingMessage = Dialog.Clean("AUTOUPDATECHECKER_CHECKING"); SortedDictionary <ModUpdateInfo, EverestModuleMetadata> updateList = ModUpdaterHelper.GetAsyncLoadedModUpdates(); if (updateList == null || updateList.Count == 0) { // no mod update, clear message and continue right away. modUpdatingMessage = null; shouldContinue = true; } else { // install mod updates autoUpdate(updateList); } }).Start(); }
public MainMenuModOptionsButton(string labelName, string iconName, Oui oui, Vector2 targetPosition, Vector2 tweenFrom, Action onConfirm) : base(labelName, iconName, oui, targetPosition, tweenFrom, onConfirm) { int delayedModCount = Everest.Loader.Delayed.Count; // if the update check failed or isn't done yet, assume there are no updates (no message in main menu). int modUpdatesAvailable = ModUpdaterHelper.IsAsyncUpdateCheckingDone() ? (ModUpdaterHelper.GetAsyncLoadedModUpdates()?.Count ?? 0) : 0; if (delayedModCount > 1) { subText = string.Format(Dialog.Get("MENU_MODOPTIONS_MULTIPLE_MODS_FAILEDTOLOAD"), delayedModCount); } else if (delayedModCount == 1) { subText = Dialog.Clean("MENU_MODOPTIONS_ONE_MOD_FAILEDTOLOAD"); } else if (Everest.Updater.HasUpdate) { subText = Dialog.Clean("MENU_MODOPTIONS_UPDATE_AVAILABLE"); } else if (modUpdatesAvailable > 1) { subText = string.Format(Dialog.Get("MENU_MODOPTIONS_MOD_UPDATES_AVAILABLE"), modUpdatesAvailable); } else if (modUpdatesAvailable == 1) { subText = Dialog.Clean("MENU_MODOPTIONS_MOD_UPDATE_AVAILABLE"); } else { subText = null; } }
protected virtual void CreateModMenuSectionHeader(TextMenu menu, bool inGame, EventInstance snapshot) { Type type = SettingsType; EverestModuleSettings settings = _Settings; if (type == null || settings == null) { return; } string typeName = type.Name.ToLowerInvariant(); if (typeName.EndsWith("settings")) { typeName = typeName.Substring(0, typeName.Length - 8); } string nameDefaultPrefix = $"modoptions_{typeName}_"; string name; // We lazily reuse this field for the props later on. name = type.GetCustomAttribute <SettingNameAttribute>()?.Name ?? $"{nameDefaultPrefix}title"; name = name.DialogCleanOrNull() ?? ModUpdaterHelper.FormatModName(Metadata.Name); menu.Add(new patch_TextMenu.patch_SubHeader(name + " | v." + Metadata.VersionString)); }
private static Scene _GetNextScene(Overworld.StartMode startMode, HiresSnow snow) { bool transitionToModUpdater = false; if (CoreModule.Settings.AutoUpdateModsOnStartup) { if (!ModUpdaterHelper.IsAsyncUpdateCheckingDone()) { // update checking is not done yet. // transition to mod updater screen to display the "checking for updates" message. transitionToModUpdater = true; } else { SortedDictionary <ModUpdateInfo, EverestModuleMetadata> modUpdates = ModUpdaterHelper.GetAsyncLoadedModUpdates(); if (modUpdates != null && modUpdates.Count != 0) { // update checking is done, and updates are available. // transition to mod updater screen in order to install the updates transitionToModUpdater = true; } } } if (transitionToModUpdater) { return(new AutoModUpdater(snow)); } else { return(new OverworldLoader(startMode, snow)); } }
/// <summary> /// Does the actual downloading of the mod. This is it's own function, to avoid double code /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="mod">The mod metadata from Everest for the installed mod</param> /// <param name="button">The button for that mod shown on the interface</param> /// <returns>Bool wether the update failed or not</returns> private bool doDownloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { // we will download the mod to Celeste_Directory/[update.GetHashCode()].zip at first. string zipPath = Path.Combine(Everest.PathGame, $"modupdate-{update.GetHashCode()}.zip"); try { // download it... button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum ModUpdaterHelper.VerifyChecksum(update, zipPath); // mark restarting as required, as we will do weird stuff like closing zips afterwards. shouldRestart = true; // install it button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_INSTALLING")})"; ModUpdaterHelper.InstallModUpdate(update, mod, zipPath); // done! button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_UPDATED")})"; return(true); } catch (Exception e) { // update failed button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_FAILED")})"; Logger.Log("OuiModUpdateList", $"Updating {update.Name} failed"); Logger.LogDetailed(e); // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); return(false); } }
/// <summary> /// Downloads a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="button">The button for that mod shown on the interface</param> /// <param name="zipPath">The path to the zip the update will be downloaded to</param> private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Func <int, long, int, bool> progressCallback = (position, length, speed) => { if (ongoingUpdateCancelled) { return(false); } if (length > 0) { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } return(true); }; try { Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, progressCallback); } catch (WebException e) { Logger.Log(LogLevel.Warn, "OuiModUpdateList", $"Download failed, trying mirror {update.MirrorURL}"); Logger.LogDetailed(e); Everest.Updater.DownloadFileWithProgress(update.MirrorURL, zipPath, progressCallback); } }
public void Fetch() { // 1. Download the mod updates database updateCatalog = ModUpdaterHelper.DownloadModUpdateList(); // 2. Find out what actually has been updated if (updateCatalog != null) { availableUpdatesCatalog = ModUpdaterHelper.ListAvailableUpdates(updateCatalog, excludeBlacklist: false); } }
/// <summary> /// Downloads and installs a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="mod">The mod metadata from Everest for the installed mod</param> /// <param name="button">The button for that mod shown on the interface</param> private void downloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { task = new Task(() => { // we will download the mod to Celeste_Directory/mod-update.zip at first. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); try { // download it... button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum ModUpdaterHelper.VerifyChecksum(update, zipPath); // mark restarting as required, as we will do weird stuff like closing zips afterwards. if (!shouldRestart) { shouldRestart = true; subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("MODUPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("MODUPDATECHECKER_WILLRESTART")})"; } // install it button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_INSTALLING")})"; ModUpdaterHelper.InstallModUpdate(update, mod, zipPath); // done! button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_UPDATED")})"; // select another enabled option: the next one, or the last one if there is no next one. if (menu.Selection + 1 > menu.LastPossibleSelection) { menu.Selection = menu.LastPossibleSelection; } else { menu.MoveSelection(1); } } catch (Exception e) { // update failed button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_FAILED")})"; Logger.Log("OuiModUpdateList", $"Updating {update.Name} failed"); Logger.LogDetailed(e); button.Disabled = false; // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); } // give the menu control back to the player menu.Focused = true; }); task.Start(); }
/// <summary> /// Downloads a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="button">The button for that mod shown on the interface</param> /// <param name="zipPath">The path to the zip the update will be downloaded to</param> private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, (position, length, speed) => { if (length > 0) { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } }); }
public override IEnumerator Enter(Oui from) { Everest.Loader.AutoLoadNewMods = false; menu = new TextMenu(); // display the title and a dummy "Fetching" button menu.Add(new TextMenu.Header(Dialog.Clean("MODUPDATECHECKER_MENU_TITLE"))); menu.Add(subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"))); fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")); fetchingButton.Disabled = true; menu.Add(fetchingButton); Scene.Add(menu); menu.Visible = Visible = true; menu.Focused = false; for (float p = 0f; p < 1f; p += Engine.DeltaTime * 4f) { menu.X = offScreenX + -1920f * Ease.CubeOut(p); alpha = Ease.CubeOut(p); yield return(null); } menu.Focused = true; menuOnScreen = true; task = new Task(() => { // 1. Download the mod updates database updateCatalog = ModUpdaterHelper.DownloadModUpdateList(); // 2. Find out what actually has been updated if (updateCatalog != null) { availableUpdatesCatalog = ModUpdaterHelper.ListAvailableUpdates(updateCatalog); } }); task.Start(); }
private IEnumerator Routine() { // display "checking for updates" message, in case the async task is not done yet. modUpdatingMessage = Dialog.Clean("AUTOUPDATECHECKER_CHECKING"); // wait until the update check is over. showCancel = true; while (!ModUpdaterHelper.IsAsyncUpdateCheckingDone() && !skipUpdate) { yield return(null); } showCancel = false; if (!skipUpdate) { SortedDictionary <ModUpdateInfo, EverestModuleMetadata> updateList = ModUpdaterHelper.GetAsyncLoadedModUpdates(); if (updateList == null || updateList.Count == 0) { // no mod update, clear message and continue right away. modUpdatingMessage = null; shouldContinue = true; } else { // install mod updates new Task(() => autoUpdate(updateList)).Start(); } // wait until we can continue (async task finished, or player hit Confirm to continue) while (!shouldContinue) { yield return(null); } } // proceed to the title screen, as GameLoader would do it normally. Engine.Scene = new OverworldLoader(Overworld.StartMode.Titlescreen, snow); }
internal static void Boot() { Logger.Log(LogLevel.Info, "core", "Booting Everest"); Logger.Log(LogLevel.Info, "core", $"AppDomain: {AppDomain.CurrentDomain.FriendlyName ?? "???"}"); Logger.Log(LogLevel.Info, "core", $"VersionCelesteString: {VersionCelesteString}"); if (Type.GetType("Mono.Runtime") != null) { // Mono hates HTTPS. ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return(true); }; } // enable TLS 1.2 to fix connecting to everestapi.github.io ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; PathGame = Path.GetDirectoryName(typeof(Celeste).Assembly.Location); // .NET hates it when strong-named dependencies get updated. AppDomain.CurrentDomain.AssemblyResolve += (asmSender, asmArgs) => { AssemblyName asmName = new AssemblyName(asmArgs.Name); if (!asmName.Name.StartsWith("Mono.Cecil") && !asmName.Name.StartsWith("YamlDotNet") && !asmName.Name.StartsWith("NLua")) { return(null); } Assembly asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(other => other.GetName().Name == asmName.Name); if (asm != null) { return(asm); } return(Assembly.LoadFrom(Path.Combine(PathGame, asmName.Name + ".dll"))); }; // .NET hates to acknowledge manually loaded assemblies. AppDomain.CurrentDomain.AssemblyResolve += (asmSender, asmArgs) => { AssemblyName asmName = new AssemblyName(asmArgs.Name); foreach (Assembly asm in _RelinkedAssemblies) { if (asm.GetName().Name == asmName.Name) { return(asm); } } return(null); }; // Preload some basic dependencies. Assembly.Load("MonoMod.RuntimeDetour"); Assembly.Load("MonoMod.Utils"); Assembly.Load("Mono.Cecil"); Assembly.Load("YamlDotNet"); Assembly.Load("Newtonsoft.Json"); Assembly.Load("Jdenticon"); if (!File.Exists(Path.Combine(PathGame, "EverestXDGFlag"))) { XDGPaths = false; PathEverest = PathGame; } else { XDGPaths = true; string dataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); Directory.CreateDirectory(PathEverest = Path.Combine(dataDir, "Everest")); Directory.CreateDirectory(Path.Combine(dataDir, "Everest", "Mods")); // Make sure it exists before content gets initialized } // Old versions of Everest have used a separate ModSettings folder. string modSettingsOld = Path.Combine(PathEverest, "ModSettings"); string modSettingsRIP = Path.Combine(PathEverest, "ModSettings-OBSOLETE"); if (Directory.Exists(modSettingsOld) || Directory.Exists(modSettingsRIP)) { Logger.Log(LogLevel.Warn, "core", "THE ModSettings FOLDER IS OBSOLETE AND WILL NO LONGER BE USED!"); if (Directory.Exists(modSettingsOld) && !Directory.Exists(modSettingsRIP)) { Directory.Move(modSettingsOld, modSettingsRIP); } } _DetourModManager = new DetourModManager(); _DetourModManager.OnILHook += (owner, from, to) => { _DetourOwners.Add(owner); object target = to.Target; _DetourLog.Add($"new ILHook by {owner.GetName().Name}: {from.GetID()} -> {to.Method?.GetID() ?? "???"}" + (target == null ? "" : $" (target: {target})")); }; _DetourModManager.OnHook += (owner, from, to, target) => { _DetourOwners.Add(owner); _DetourLog.Add($"new Hook by {owner.GetName().Name}: {from.GetID()} -> {to.GetID()}" + (target == null ? "" : $" (target: {target})")); }; _DetourModManager.OnDetour += (owner, from, to) => { _DetourOwners.Add(owner); _DetourLog.Add($"new Detour by {owner.GetName().Name}: {from.GetID()} -> {to.GetID()}"); }; _DetourModManager.OnNativeDetour += (owner, fromMethod, from, to) => { _DetourOwners.Add(owner); _DetourLog.Add($"new NativeDetour by {owner.GetName().Name}: {fromMethod?.ToString() ?? from.ToString("16X")} -> {to.ToString("16X")}"); }; HookEndpointManager.OnAdd += (from, to) => { Assembly owner = HookEndpointManager.GetOwner(to) as Assembly ?? typeof(Everest).Assembly; _DetourOwners.Add(owner); object target = to.Target; _DetourLog.Add($"new On.+= by {owner.GetName().Name}: {from.GetID()} -> {to.Method?.GetID() ?? "???"}" + (target == null ? "" : $" (target: {target})")); return(true); }; HookEndpointManager.OnModify += (from, to) => { Assembly owner = HookEndpointManager.GetOwner(to) as Assembly ?? typeof(Everest).Assembly; _DetourOwners.Add(owner); object target = to.Target; _DetourLog.Add($"new IL.+= by {owner.GetName().Name}: {from.GetID()} -> {to.Method?.GetID() ?? "???"}" + (target == null ? "" : $" (target: {target})")); return(true); }; // Before even initializing anything else, make sure to prepare any static flags. Flags.Initialize(); // 0.1 parses into 1 in regions using , // This also somehow sets the exception message language to English. CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; if (!Flags.IsHeadless) { // Initialize the content helper. Content.Initialize(); // Initialize all main managers before loading any mods. TouchInputManager.Instance = new TouchInputManager(Celeste.Instance); // Don't add it yet, though - add it in Initialize. } MainThreadHelper.Instance = new MainThreadHelper(Celeste.Instance); // Register our core module and load any other modules. new CoreModule().Register(); // Note: Everest fulfills some mod dependencies by itself. new NullModule(new EverestModuleMetadata() { Name = "Celeste", VersionString = $"{Celeste.Instance.Version.ToString()}-{(typeof(Game).Assembly.FullName.Contains("FNA") ? "fna" : "xna")}" }).Register(); new NullModule(new EverestModuleMetadata() { Name = "DialogCutscene", VersionString = "1.0.0" }).Register(); new NullModule(new EverestModuleMetadata() { Name = "UpdateChecker", VersionString = "1.0.2" }).Register(); LuaLoader.Initialize(); Loader.LoadAuto(); if (!Flags.IsHeadless) { // Load stray .bins afterwards. Content.Crawl(new MapBinsInModsModContent(Path.Combine(PathEverest, "Mods"))); } // Also let all mods parse the arguments. Queue <string> args = new Queue <string>(Args); while (args.Count > 0) { string arg = args.Dequeue(); foreach (EverestModule mod in _Modules) { if (mod.ParseArg(arg, args)) { break; } } } // Start requesting the version list ASAP. Updater.RequestAll(); // Request the mod update list as well. ModUpdaterHelper.RunAsyncCheckForModUpdates(excludeBlacklist: true); }
private void downloadAllDependencies() { // 1. Compute the list of dependencies we must download. LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_DOWNLOADING_DATABASE")); Everest.Updater.Entry everestVersionToInstall = null; Dictionary <string, ModUpdateInfo> availableDownloads = ModUpdaterHelper.DownloadModUpdateList(); if (availableDownloads == null) { shouldAutoExit = false; shouldRestart = false; LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_DOWNLOAD_DATABASE_FAILED")); } else { // load information on all installed mods, so that we can spot blacklisted ones easily. LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_LOADING_INSTALLED_MODS")); Progress = 0; ProgressMax = 100; Dictionary <string, EverestModuleMetadata[]> allModsInformationFlipped = OuiModToggler.LoadAllModYamls(progress => { Lines[Lines.Count - 1] = $"{Dialog.Clean("DEPENDENCYDOWNLOADER_LOADING_INSTALLED_MODS")} ({(int) (progress * 100)}%)"; Progress = (int)(progress * 100); }); ProgressMax = 0; // but flip around the mapping for convenience. Dictionary <EverestModuleMetadata, string> allModsInformation = new Dictionary <EverestModuleMetadata, string>(); foreach (KeyValuePair <string, EverestModuleMetadata[]> mods in allModsInformationFlipped) { foreach (EverestModuleMetadata mod in mods.Value) { allModsInformation[mod] = mods.Key; } } Lines[Lines.Count - 1] = $"{Dialog.Clean("DEPENDENCYDOWNLOADER_LOADING_INSTALLED_MODS")} {Dialog.Clean("DEPENDENCYDOWNLOADER_DONE")}"; Logger.Log("OuiDependencyDownloader", "Computing dependencies to download..."); // these mods are not installed currently, we will install them Dictionary <string, ModUpdateInfo> modsToInstall = new Dictionary <string, ModUpdateInfo>(); // these mods are already installed, but need an update to satisfy the dependency Dictionary <string, ModUpdateInfo> modsToUpdate = new Dictionary <string, ModUpdateInfo>(); Dictionary <string, EverestModuleMetadata> modsToUpdateCurrentVersions = new Dictionary <string, EverestModuleMetadata>(); // Everest should be updated to satisfy a dependency on Everest bool shouldUpdateEverestManually = false; // these mods are absent from the database HashSet <string> modsNotFound = new HashSet <string>(); // these mods have multiple downloads, and as such, should be installed manually HashSet <string> modsNotInstallableAutomatically = new HashSet <string>(); // these mods should be unblacklisted. HashSet <string> modFilenamesToUnblacklist = new HashSet <string>(); // these mods are in the database, but the version found in there won't satisfy the dependency Dictionary <string, HashSet <Version> > modsWithIncompatibleVersionInDatabase = new Dictionary <string, HashSet <Version> >(); Dictionary <string, string> modsDatabaseVersions = new Dictionary <string, string>(); foreach (EverestModuleMetadata dependency in MissingDependencies) { if (Everest.Loader.Delayed.Any(delayedMod => dependency.Name == delayedMod.Item1.Name)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is installed but failed to load, skipping"); } else if (dependency.Name == "Everest") { Logger.Log("OuiDependencyDownloader", $"Everest should be updated"); shouldAutoExit = false; if (dependency.Version.Major != 1 || dependency.Version.Build > 0 || dependency.Version.Revision > 0) { // the Everest version is not 1.XXX.0.0: Everest should be updated manually because this shouldn't happen. shouldUpdateEverestManually = true; } else if (!shouldUpdateEverestManually && (everestVersionToInstall == null || everestVersionToInstall.Build < dependency.Version.Minor)) { everestVersionToInstall = findEverestVersionToInstall(dependency.Version.Minor); if (everestVersionToInstall == null) { // a suitable version was not found! so, it should be installed manually. shouldUpdateEverestManually = true; } } } else if (tryUnblacklist(dependency, allModsInformation, modFilenamesToUnblacklist)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is blacklisted, and should be unblacklisted instead"); } else if (!availableDownloads.ContainsKey(dependency.Name)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} was not found in the database"); modsNotFound.Add(dependency.Name); shouldAutoExit = false; } else if (availableDownloads[dependency.Name].xxHash.Count > 1) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} has multiple versions and cannot be installed automatically"); modsNotInstallableAutomatically.Add(dependency.Name); shouldAutoExit = false; } else if (!isVersionCompatible(dependency.Version, availableDownloads[dependency.Name].Version)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} has a version in database ({availableDownloads[dependency.Name].Version}) that would not satisfy dependency ({dependency.Version})"); // add the required version to the list of versions for this mod HashSet <Version> requiredVersions = modsWithIncompatibleVersionInDatabase.TryGetValue(dependency.Name, out HashSet <Version> result) ? result : new HashSet <Version>(); requiredVersions.Add(dependency.Version); modsWithIncompatibleVersionInDatabase[dependency.Name] = requiredVersions; modsDatabaseVersions[dependency.Name] = availableDownloads[dependency.Name].Version; shouldAutoExit = false; } else { EverestModuleMetadata installedVersion = null; foreach (EverestModule module in Everest.Modules) { // note: if the mod is installed, but not as a zip, this will be treated as a fresh install rather than an update. // this is fine since zips take the priority over directories, and we cannot update directory mods anyway. if (module.Metadata.PathArchive != null && module.Metadata.Name == dependency.Name) { installedVersion = module.Metadata; break; } } if (installedVersion != null) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is already installed and will be updated"); modsToUpdate[dependency.Name] = availableDownloads[dependency.Name]; modsToUpdateCurrentVersions[dependency.Name] = installedVersion; } else { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} will be installed"); modsToInstall[dependency.Name] = availableDownloads[dependency.Name]; } } } // actually install the mods now foreach (ModUpdateInfo modToInstall in modsToInstall.Values) { downloadDependency(modToInstall, null); } foreach (ModUpdateInfo modToUpdate in modsToUpdate.Values) { downloadDependency(modToUpdate, modsToUpdateCurrentVersions[modToUpdate.Name]); } // unblacklist mods if this is needed if (modFilenamesToUnblacklist.Count > 0) { // remove the mods from blacklist.txt if (!unblacklistMods(modFilenamesToUnblacklist)) { // something bad happened shouldAutoExit = false; shouldRestart = true; } foreach (string modFilename in modFilenamesToUnblacklist) { try { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_UNBLACKLIST"), modFilename)); // remove the mod from the loaded blacklist while (Everest.Loader._Blacklist.Contains(modFilename)) { Everest.Loader._Blacklist.Remove(modFilename); } // hot load the mod if (modFilename.EndsWith(".zip")) { Everest.Loader.LoadZip(Path.Combine(Everest.Loader.PathMods, modFilename)); } else { Everest.Loader.LoadDir(Path.Combine(Everest.Loader.PathMods, modFilename)); } } catch (Exception e) { // something bad happened during the mod hot loading, log it and prompt to restart the game to load the mod. LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_UNBLACKLIST_FAILED")); Logger.LogDetailed(e); shouldAutoExit = false; shouldRestart = true; break; } } } // display all mods that couldn't be accounted for if (shouldUpdateEverestManually) { LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_MUST_UPDATE_EVEREST")); } foreach (string mod in modsNotFound) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_NOT_FOUND"), mod)); } foreach (string mod in modsNotInstallableAutomatically) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_NOT_AUTO_INSTALLABLE"), mod)); } foreach (string mod in modsWithIncompatibleVersionInDatabase.Keys) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_WRONG_VERSION"), mod, string.Join(", ", modsWithIncompatibleVersionInDatabase[mod]), modsDatabaseVersions[mod])); } } Progress = 1; ProgressMax = 1; if (shouldAutoExit) { // there are no errors to display: restart automatically if (shouldRestart) { LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING")); for (int i = 3; i > 0; --i) { Lines[Lines.Count - 1] = string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_RESTARTING_IN"), i); Thread.Sleep(1000); } Lines[Lines.Count - 1] = Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING"); Everest.QuickFullRestart(); } else { Exit(); } } else if (everestVersionToInstall != null) { LogLine("\n" + string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_EVEREST_UPDATE"), everestVersionToInstall.Build)); this.everestVersionToInstall = everestVersionToInstall; } else if (shouldRestart) { LogLine("\n" + Dialog.Clean("DEPENDENCYDOWNLOADER_PRESS_BACK_TO_RESTART")); } else { LogLine("\n" + Dialog.Clean("DEPENDENCYDOWNLOADER_PRESS_BACK_TO_GO_BACK")); } }
private void autoUpdate(SortedDictionary <ModUpdateInfo, EverestModuleMetadata> updateList) { int currentlyUpdatedModIndex = 1; // this is common to all mods. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); bool restartRequired = false; bool failuresOccured = false; // iterate through all mods to update now. foreach (ModUpdateInfo update in updateList.Keys) { // common beginning for all messages: f.e. [1/3] Auto-updating Polygon Dreams string progressString = $"[{currentlyUpdatedModIndex}/{updateList.Count}] {Dialog.Clean("AUTOUPDATECHECKER_UPDATING")} {update.Name.SpacedPascalCase()}:"; try { // download it... modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")}"; Logger.Log("AutoModUpdater", $"Downloading {update.URL} to {zipPath}"); Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, (position, length, speed) => { if (length > 0) { modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")} " + $"({((int)Math.Floor(100D * (position / (double)length)))}% @ {speed} KiB/s)"; } else { modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")} " + $"({((int)Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } }); // verify its checksum modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_VERIFYING")}"; ModUpdaterHelper.VerifyChecksum(update, zipPath); // install it restartRequired = true; modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_INSTALLING")}"; ModUpdaterHelper.InstallModUpdate(update, updateList[update], zipPath); } catch (Exception e) { // update failed Logger.Log("AutoModUpdater", $"Updating {update.Name} failed"); Logger.LogDetailed(e); failuresOccured = true; // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); } currentlyUpdatedModIndex++; } if (!failuresOccured) { // restart when everything is done modUpdatingMessage = Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING"); Thread.Sleep(1000); Everest.QuickFullRestart(); } else { modUpdatingMessage = Dialog.Clean("AUTOUPDATECHECKER_FAILED"); // stop the cogwheel cogwheelSpinning = false; cogwheelStopTime = RawTimeActive; if (restartRequired) { // failures occured, restart is required modUpdatingSubMessage = Dialog.Clean("AUTOUPDATECHECKER_REBOOT"); confirmToRestart = true; } else { // failures occured, restart is not required modUpdatingSubMessage = Dialog.Clean("AUTOUPDATECHECKER_CONTINUE"); confirmToContinue = true; } } }
private void autoUpdate(SortedDictionary <ModUpdateInfo, EverestModuleMetadata> updateList) { int currentlyUpdatedModIndex = 1; // this is common to all mods. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); bool restartRequired = false; bool failuresOccured = false; // iterate through all mods to update now. foreach (ModUpdateInfo update in updateList.Keys) { // common beginning for all messages: f.e. [1/3] Auto-updating Polygon Dreams string progressString = $"[{currentlyUpdatedModIndex}/{updateList.Count}] {Dialog.Clean("AUTOUPDATECHECKER_UPDATING")} {ModUpdaterHelper.FormatModName(update.Name)}:"; try { // show the cancel button for downloading showCancel = true; // download it... modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")}"; Logger.Log("AutoModUpdater", $"Downloading {update.URL} to {zipPath}"); Func <int, long, int, bool> progressCallback = (position, length, speed) => { if (skipUpdate) { return(false); } if (length > 0) { modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")} " + $"({((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s)"; } else { modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_DOWNLOADING")} " + $"({((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } return(true); }; try { Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, progressCallback); } catch (WebException e) { Logger.Log(LogLevel.Warn, "AutoModUpdater", $"Download failed, trying mirror {update.MirrorURL}"); Logger.LogDetailed(e); Everest.Updater.DownloadFileWithProgress(update.MirrorURL, zipPath, progressCallback); } // hide the cancel button for downloading, download is done showCancel = false; if (skipUpdate) { Logger.Log("AutoModUpdater", "Update was skipped"); // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); if (restartRequired) { // stop trying to update mods; restart right away break; } else { // proceed to the game right away shouldContinue = true; return; } } // verify its checksum modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_VERIFYING")}"; ModUpdaterHelper.VerifyChecksum(update, zipPath); // install it restartRequired = true; modUpdatingMessage = $"{progressString} {Dialog.Clean("AUTOUPDATECHECKER_INSTALLING")}"; ModUpdaterHelper.InstallModUpdate(update, updateList[update], zipPath); } catch (Exception e) { // update failed Logger.Log("AutoModUpdater", $"Updating {update.Name} failed"); Logger.LogDetailed(e); failuresOccured = true; // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); // stop trying to update further mods. break; } currentlyUpdatedModIndex++; } // don't show "cancel" anymore, install ended. showCancel = false; if (!failuresOccured) { // restart when everything is done modUpdatingMessage = Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING"); Thread.Sleep(1000); Everest.QuickFullRestart(); } else { modUpdatingMessage = Dialog.Clean("AUTOUPDATECHECKER_FAILED"); // stop the cogwheel cogwheelSpinning = false; cogwheelStopTime = RawTimeActive; if (restartRequired) { // failures occured, restart is required modUpdatingSubMessage = Dialog.Clean("AUTOUPDATECHECKER_REBOOT"); confirmToRestart = true; } else { // failures occured, restart is not required modUpdatingSubMessage = Dialog.Clean("AUTOUPDATECHECKER_CONTINUE"); confirmToContinue = true; } } }
public override void Update() { // check if the "press Back to restart" message has to be toggled if (menu != null && subHeader != null) { if (menu.Focused && shouldRestart) { subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("MODUPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("MODUPDATECHECKER_WILLRESTART")})"; } else if (!menu.Focused && ongoingUpdateCancelled && menuOnScreen) { subHeader.TextColor = Color.Gray; subHeader.Title = $"{Dialog.Clean("MODUPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("MODUPDATECHECKER_CANCELLING")})"; } else { subHeader.TextColor = Color.Gray; subHeader.Title = Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"); } } if (menu != null && task != null && task.IsCompleted) { // there is no download or install task in progress if (fetchingButton != null) { // This means fetching the updates just finished. We have to remove the "Checking for updates" button // and put the actual update list instead. Logger.Log("OuiModUpdateList", "Rendering updates"); menu.Remove(fetchingButton); fetchingButton = null; if (updateCatalog == null) { // display an error message TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_ERROR")); button.Disabled = true; menu.Add(button); } else if (availableUpdatesCatalog.Count == 0) { // display a dummy "no update available" button TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_NOUPDATE")); button.Disabled = true; menu.Add(button); } else { // if there are multiple updates... if (availableUpdatesCatalog.Count > 1) { // display an "update all" button at the top of the list updateAllButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_UPDATE_ALL")); updateAllButton.Pressed(() => downloadAllMods()); menu.Add(updateAllButton); } // then, display one button per update foreach (ModUpdateInfo update in availableUpdatesCatalog.Keys) { EverestModuleMetadata metadata = availableUpdatesCatalog[update]; string versionUpdate = metadata.VersionString; if (metadata.VersionString != update.Version) { versionUpdate = $"{metadata.VersionString} > {update.Version}"; } TextMenu.Button button = new TextMenu.Button($"{ModUpdaterHelper.FormatModName(metadata.Name)} | v. {versionUpdate} ({new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(update.LastUpdate):yyyy-MM-dd})"); button.Pressed(() => { // make the menu non-interactive menu.Focused = false; button.Disabled = true; // trigger the update download downloadModUpdate(update, metadata, button); }); // if there is more than one hash, it means there is multiple downloads for this mod. Thus, we can't update it manually. // if there isn't, add it to the list of mods that can be updated via "update all" if (update.xxHash.Count > 1) { button.Disabled = true; } else { updatableMods.Add(new ModUpdateHolder() { update = update, metadata = metadata, button = button }); } menu.Add(button); } } } if (menu.Focused && Selected && Input.MenuCancel.Pressed) { if (shouldRestart) { Everest.QuickFullRestart(); } else { // go back to mod options instead Audio.Play(SFX.ui_main_button_back); Overworld.Goto <OuiModOptions>(); } } } if (Input.MenuCancel.Pressed) { // cancel any ongoing download (this has no effect if no download is ongoing anyway). ongoingUpdateCancelled = true; } base.Update(); }
private void downloadAllDependencies() { // 1. Compute the list of dependencies we must download. LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_DOWNLOADING_DATABASE")); Dictionary <string, ModUpdateInfo> availableDownloads = ModUpdaterHelper.DownloadModUpdateList(); if (availableDownloads == null) { shouldAutoRestart = false; shouldRestart = false; LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_DOWNLOAD_DATABASE_FAILED")); } else { Logger.Log("OuiDependencyDownloader", "Computing dependencies to download..."); // these mods are not installed currently, we will install them Dictionary <string, ModUpdateInfo> modsToInstall = new Dictionary <string, ModUpdateInfo>(); // these mods are already installed, but need an update to satisfy the dependency Dictionary <string, ModUpdateInfo> modsToUpdate = new Dictionary <string, ModUpdateInfo>(); Dictionary <string, EverestModuleMetadata> modsToUpdateCurrentVersions = new Dictionary <string, EverestModuleMetadata>(); // Everest should be updated to satisfy a dependency on Everest bool shouldUpdateEverest = false; // these mods are absent from the database HashSet <string> modsNotFound = new HashSet <string>(); // these mods have multiple downloads, and as such, should be installed manually HashSet <string> modsNotInstallableAutomatically = new HashSet <string>(); // these mods are blacklisted, and should be removed from the blacklist instead of being re-installed HashSet <string> modsBlacklisted = new HashSet <string>(); // these mods are in the database, but the version found in there won't satisfy the dependency Dictionary <string, HashSet <Version> > modsWithIncompatibleVersionInDatabase = new Dictionary <string, HashSet <Version> >(); Dictionary <string, string> modsDatabaseVersions = new Dictionary <string, string>(); foreach (EverestModuleMetadata dependency in MissingDependencies) { if (Everest.Loader.Delayed.Any(delayedMod => dependency.Name == delayedMod.Item1.Name)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is installed but failed to load, skipping"); } else if (dependency.Name == "Everest") { Logger.Log("OuiDependencyDownloader", $"Everest should be updated"); shouldUpdateEverest = true; shouldAutoRestart = false; // TODO: maybe check more precisely for blacklisted mods? We're only basing ourselves on the name here. } else if (Everest.Loader.Blacklist.Contains($"{dependency.Name}.zip")) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is blacklisted, and should be unblacklisted instead"); modsBlacklisted.Add(dependency.Name); shouldAutoRestart = false; } else if (!availableDownloads.ContainsKey(dependency.Name)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} was not found in the database"); modsNotFound.Add(dependency.Name); shouldAutoRestart = false; } else if (availableDownloads[dependency.Name].xxHash.Count > 1) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} has multiple versions and cannot be installed automatically"); modsNotInstallableAutomatically.Add(dependency.Name); shouldAutoRestart = false; } else if (!isVersionCompatible(dependency.Version, availableDownloads[dependency.Name].Version)) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} has a version in database ({availableDownloads[dependency.Name].Version}) that would not satisfy dependency ({dependency.Version})"); // add the required version to the list of versions for this mod HashSet <Version> requiredVersions = modsWithIncompatibleVersionInDatabase.TryGetValue(dependency.Name, out HashSet <Version> result) ? result : new HashSet <Version>(); requiredVersions.Add(dependency.Version); modsWithIncompatibleVersionInDatabase[dependency.Name] = requiredVersions; modsDatabaseVersions[dependency.Name] = availableDownloads[dependency.Name].Version; shouldAutoRestart = false; } else { EverestModuleMetadata installedVersion = null; foreach (EverestModule module in Everest.Modules) { // note: if the mod is installed, but not as a zip, this will be treated as a fresh install rather than an update. // this is fine since zips take the priority over directories, and we cannot update directory mods anyway. if (module.Metadata.PathArchive != null && module.Metadata.Name == dependency.Name) { installedVersion = module.Metadata; break; } } if (installedVersion != null) { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} is already installed and will be updated"); modsToUpdate[dependency.Name] = availableDownloads[dependency.Name]; modsToUpdateCurrentVersions[dependency.Name] = installedVersion; } else { Logger.Log("OuiDependencyDownloader", $"{dependency.Name} will be installed"); modsToInstall[dependency.Name] = availableDownloads[dependency.Name]; } } } // actually install the mods now foreach (ModUpdateInfo modToInstall in modsToInstall.Values) { downloadDependency(modToInstall, null); } foreach (ModUpdateInfo modToUpdate in modsToUpdate.Values) { downloadDependency(modToUpdate, modsToUpdateCurrentVersions[modToUpdate.Name]); } // display all mods that couldn't be accounted for if (shouldUpdateEverest) { LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_MUST_UPDATE_EVEREST")); } foreach (string mod in modsNotFound) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_NOT_FOUND"), mod)); } foreach (string mod in modsNotInstallableAutomatically) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_NOT_AUTO_INSTALLABLE"), mod)); } foreach (string mod in modsBlacklisted) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_BLACKLISTED"), mod)); } foreach (string mod in modsWithIncompatibleVersionInDatabase.Keys) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_MOD_WRONG_VERSION"), mod, string.Join(", ", modsWithIncompatibleVersionInDatabase[mod]), modsDatabaseVersions[mod])); } } Progress = 1; ProgressMax = 1; if (shouldAutoRestart) { // there are no errors to display: restart automatically LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING")); for (int i = 3; i > 0; --i) { Lines[Lines.Count - 1] = string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_RESTARTING_IN"), i); Thread.Sleep(1000); } Lines[Lines.Count - 1] = Dialog.Clean("DEPENDENCYDOWNLOADER_RESTARTING"); Everest.QuickFullRestart(); } else if (shouldRestart) { LogLine("\n" + Dialog.Clean("DEPENDENCYDOWNLOADER_PRESS_BACK_TO_RESTART")); } else { LogLine("\n" + Dialog.Clean("DEPENDENCYDOWNLOADER_PRESS_BACK_TO_GO_BACK")); } }
private void downloadDependency(ModUpdateInfo mod, EverestModuleMetadata installedVersion) { string downloadDestination = Path.Combine(Everest.PathGame, $"dependency-download.zip"); try { // 1. Download LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_DOWNLOADING"), mod.Name, mod.URL)); LogLine("", false); Everest.Updater.DownloadFileWithProgress(mod.URL, downloadDestination, (position, length, speed) => { if (length > 0) { Lines[Lines.Count - 1] = $"{((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s"; Progress = position; ProgressMax = (int)length; } else { Lines[Lines.Count - 1] = $"{((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s"; ProgressMax = 0; } }); ProgressMax = 0; Lines[Lines.Count - 1] = Dialog.Clean("DEPENDENCYDOWNLOADER_DOWNLOAD_FINISHED"); // 2. Verify checksum LogLine(Dialog.Clean("DEPENDENCYDOWNLOADER_VERIFYING_CHECKSUM")); ModUpdaterHelper.VerifyChecksum(mod, downloadDestination); // 3. Install mod shouldRestart = true; if (installedVersion != null) { LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_UPDATING"), mod.Name, installedVersion.Version, mod.Version, installedVersion.PathArchive)); ModUpdaterHelper.InstallModUpdate(mod, installedVersion, downloadDestination); } else { string installDestination = Path.Combine(Everest.Loader.PathMods, $"{mod.Name}.zip"); LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_INSTALLING"), mod.Name, mod.Version, installDestination)); File.Move(downloadDestination, installDestination); } } catch (Exception e) { // install failed LogLine(string.Format(Dialog.Get("DEPENDENCYDOWNLOADER_INSTALL_FAILED"), mod.Name)); Logger.LogDetailed(e); shouldAutoRestart = false; // try to delete the file if it still exists. if (File.Exists(downloadDestination)) { try { Logger.Log("OuiDependencyDownloader", $"Deleting temp file {downloadDestination}"); File.Delete(downloadDestination); } catch (Exception) { Logger.Log("OuiDependencyDownloader", $"Removing {downloadDestination} failed"); } } } }