Esempio n. 1
0
        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);

            currentCheckForUpdates = new CheckForUpdates();
            task = new Task(() => currentCheckForUpdates.Fetch());
            task.Start();

            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;
        }
Esempio n. 2
0
        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();
        }
Esempio n. 3
0
        protected override void addOptionsToMenu(TextMenu menu)
        {
            // if there is a whitelist, warn the user that it will break those settings.
            if (Everest.Loader.Whitelist != null)
            {
                menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_WHITELISTWARN"))
                {
                    TextColor = Color.OrangeRed
                });
            }

            // display the warning about blacklist.txt + restarting
            menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_1")));
            menu.Add(restartMessage2 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_2"))
            {
                HeightExtra = 0f
            });

            // reduce spacing between the whitelist warning and the blacklist overwrite warning
            if (Everest.Loader.Whitelist != null)
            {
                restartMessage1.HeightExtra = 30f;
            }

            // "enable all" and "disable all" buttons
            List <TextMenu.OnOff> allToggles = new List <TextMenu.OnOff>();

            menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_ENABLEALL")).Pressed(() => {
                foreach (TextMenu.OnOff toggle in allToggles)
                {
                    if (toggle.Index != 1)
                    {
                        toggle.Index = 1;
                        toggle.OnValueChange(true);
                    }
                }
            }));
            menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => {
                foreach (TextMenu.OnOff toggle in allToggles)
                {
                    if (toggle.Index != 0)
                    {
                        toggle.Index = 0;
                        toggle.OnValueChange(false);
                    }
                }
            }));

            // "cancel" button to leave the screen without saving
            menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => {
                blacklistedMods = blacklistedModsOriginal;
                onBackPressed(Overworld);
            }));

            // reset the mods list
            allMods         = new List <string>();
            blacklistedMods = new HashSet <string>();

            string[] files;
            bool     headerInserted;

            // crawl directories
            files          = Directory.GetDirectories(Everest.Loader.PathMods);
            headerInserted = false;
            for (int i = 0; i < files.Length; i++)
            {
                string file = Path.GetFileName(files[i]);
                if (file != "Cache")
                {
                    if (!headerInserted)
                    {
                        menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_DIRECTORIES")));
                        headerInserted = true;
                    }
                    allToggles.Add(addFileToMenu(menu, file));
                }
            }

            // crawl zips
            files          = Directory.GetFiles(Everest.Loader.PathMods);
            headerInserted = false;
            for (int i = 0; i < files.Length; i++)
            {
                string file = Path.GetFileName(files[i]);
                if (file.EndsWith(".zip"))
                {
                    if (!headerInserted)
                    {
                        menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_ZIPS")));
                        headerInserted = true;
                    }
                    allToggles.Add(addFileToMenu(menu, file));
                }
            }

            // crawl map bins
            files          = Directory.GetFiles(Everest.Loader.PathMods);
            headerInserted = false;
            for (int i = 0; i < files.Length; i++)
            {
                string file = Path.GetFileName(files[i]);
                if (file.EndsWith(".bin"))
                {
                    if (!headerInserted)
                    {
                        menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_BINS")));
                        headerInserted = true;
                    }
                    allToggles.Add(addFileToMenu(menu, file));
                }
            }

            // sort the mods list alphabetically, for output in the blacklist.txt file later.
            allMods.Sort();

            // clone the list to be able to check if the list changed when leaving the menu.
            blacklistedModsOriginal = new HashSet <string>(blacklistedMods);
        }
Esempio n. 4
0
        protected override void addOptionsToMenu(TextMenu menu)
        {
            // for now, display a "loading" message.
            TextMenu.Button loading = new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_LOADING"))
            {
                Disabled = true
            };
            menu.Add(loading);

            modLoadingTask = new Task(() => {
                // load all the mod yamls (that can take some time), update the progress every 500ms so that the text doesn't go crazy since it is centered.
                Stopwatch updateTimer = Stopwatch.StartNew();
                modYamls = LoadAllModYamls(progress => {
                    if (updateTimer.ElapsedMilliseconds > 500)
                    {
                        updateTimer.Restart();
                        loading.Label = $"{Dialog.Clean("MODOPTIONS_MODTOGGLE_LOADING")} ({(int) (progress * 100)}%)";
                    }
                });
                updateTimer.Stop();

                MainThreadHelper.Do(() => {
                    modToggles = new Dictionary <string, TextMenu.OnOff>();

                    // remove the "loading..." message
                    menu.Remove(loading);

                    // if there is a whitelist, warn the user that it will break those settings.
                    if (Everest.Loader.Whitelist != null)
                    {
                        menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_WHITELISTWARN"))
                        {
                            TextColor = Color.OrangeRed
                        });
                    }

                    // display the warning about blacklist.txt + restarting
                    menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_1")));
                    menu.Add(restartMessage2 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_2"))
                    {
                        HeightExtra = 0f
                    });
                    menu.Add(new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_3"))
                    {
                        HeightExtra = 20f, TextColor = Color.Goldenrod
                    });

                    // reduce spacing between the whitelist warning and the blacklist overwrite warning
                    if (Everest.Loader.Whitelist != null)
                    {
                        restartMessage1.HeightExtra = 30f;
                    }

                    // "enable all" and "disable all" buttons
                    menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_ENABLEALL")).Pressed(() => {
                        foreach (TextMenu.OnOff toggle in modToggles.Values)
                        {
                            toggle.Index = 1;
                        }
                        blacklistedMods.Clear();
                        updateHighlightedMods();
                    }));
                    menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => {
                        blacklistedMods.Clear();
                        foreach (KeyValuePair <string, TextMenu.OnOff> toggle in modToggles)
                        {
                            toggle.Value.Index = 0;
                            blacklistedMods.Add(toggle.Key);
                        }
                        updateHighlightedMods();
                    }));

                    // "toggle dependencies automatically" button
                    TextMenu.Item toggleDependenciesButton;
                    menu.Add(toggleDependenciesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS"), true)
                                                        .Change(value => toggleDependencies = value));

                    toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2"));
                    toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1"));

                    // "cancel" button to leave the screen without saving
                    menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => {
                        blacklistedMods = blacklistedModsOriginal;
                        onBackPressed(Overworld);
                    }));

                    // reset the mods list
                    allMods         = new List <string>();
                    blacklistedMods = new HashSet <string>();

                    string[] files;
                    bool headerInserted;

                    // crawl directories
                    files = Directory.GetDirectories(Everest.Loader.PathMods);
                    Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant()));
                    headerInserted = false;
                    for (int i = 0; i < files.Length; i++)
                    {
                        string file = Path.GetFileName(files[i]);
                        if (file != "Cache")
                        {
                            if (!headerInserted)
                            {
                                menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_DIRECTORIES")));
                                headerInserted = true;
                            }
                            addFileToMenu(menu, file);
                        }
                    }

                    // crawl zips
                    files = Directory.GetFiles(Everest.Loader.PathMods);
                    Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant()));
                    headerInserted = false;
                    for (int i = 0; i < files.Length; i++)
                    {
                        string file = Path.GetFileName(files[i]);
                        if (file.EndsWith(".zip"))
                        {
                            if (!headerInserted)
                            {
                                menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_ZIPS")));
                                headerInserted = true;
                            }
                            addFileToMenu(menu, file);
                        }
                    }

                    // crawl map bins
                    files = Directory.GetFiles(Everest.Loader.PathMods);
                    Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant()));
                    headerInserted = false;
                    for (int i = 0; i < files.Length; i++)
                    {
                        string file = Path.GetFileName(files[i]);
                        if (file.EndsWith(".bin"))
                        {
                            if (!headerInserted)
                            {
                                menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_BINS")));
                                headerInserted = true;
                            }
                            addFileToMenu(menu, file);
                        }
                    }

                    // sort the mods list alphabetically, for output in the blacklist.txt file later.
                    allMods.Sort((a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant()));

                    // adjust the mods' color if they are required dependencies for other mods
                    foreach (KeyValuePair <string, TextMenu.OnOff> toggle in modToggles)
                    {
                        if (modHasDependencies(toggle.Key))
                        {
                            ((patch_TextMenu.patch_Option <bool>)(object) toggle.Value).UnselectedColor = Color.Goldenrod;
                        }
                    }

                    // snap the menu so that it doesn't show a scroll up.
                    menu.Y = menu.ScrollTargetY;

                    // clone the list to be able to check if the list changed when leaving the menu.
                    blacklistedModsOriginal = new HashSet <string>(blacklistedMods);

                    // loading is done!
                    modLoadingTask = null;
                });
            });
            modLoadingTask.Start();
        }
Esempio n. 5
0
        private void ReloadItems()
        {
            foreach (TextMenu.Item item in items)
            {
                menu.Remove(item);
            }
            items.Clear();

            string filterSet = null;

            if (type == 0)
            {
                filterSet = "Celeste";
            }
            else if (type >= 2)
            {
                filterSet = sets[type - 2];
            }

            string        lastLevelSet          = null;
            LevelSetStats levelSetStats         = null;
            int           levelSetAreaOffset    = 0;
            int           levelSetUnlockedAreas = int.MaxValue;
            int           levelSetUnlockedModes = int.MaxValue;
            string        name;

            List <AreaStats> areaStatsAll = SaveData.Instance.Areas;

            for (int i = 0; i < AreaData.Areas.Count; i++)
            {
                AreaData area = AreaData.Areas[i];
                if (!area.HasMode((AreaMode)side))
                {
                    continue;
                }

                string levelSet = area.GetLevelSet();

                if ((filterSet == null && levelSet == "Celeste") || (filterSet != null && filterSet != levelSet))
                {
                    continue;
                }

                if (lastLevelSet != levelSet)
                {
                    lastLevelSet          = levelSet;
                    levelSetStats         = SaveData.Instance.GetLevelSetStatsFor(levelSet);
                    levelSetAreaOffset    = levelSetStats.AreaOffset;
                    levelSetUnlockedAreas = levelSetStats.UnlockedAreas;
                    levelSetUnlockedModes = levelSetStats.UnlockedModes;
                    if (levelSet != "Celeste")
                    {
                        name = DialogExt.CleanLevelSet(levelSet);
                        TextMenuExt.SubHeaderExt levelSetHeader = new TextMenuExt.SubHeaderExt(name);
                        levelSetHeader.Alpha = 0f;
                        menu.Add(levelSetHeader);
                        items.Add(levelSetHeader);
                    }
                }

                name = area.Name;
                name = name.DialogCleanOrNull() ?? name.SpacedPascalCase();

                TextMenuExt.ButtonExt button = new TextMenuExt.ButtonExt(name);
                button.Alpha = 0f;

                if (area.Icon != "areas/null")
                {
                    button.Icon = area.Icon;
                }
                button.IconWidth = 64f;

                if (levelSet == "Celeste" && i > levelSetAreaOffset + levelSetUnlockedAreas)
                {
                    button.Disabled = true;
                }
                if (side == 1 && !areaStatsAll[i].Cassette)
                {
                    button.Disabled = true;
                }
                if (side >= 2 && levelSetUnlockedModes < (side + 1))
                {
                    button.Disabled = true;
                }

                menu.Add(button.Pressed(() => {
                    Inspect(area, (AreaMode)side);
                }));
                items.Add(button);
            }

            // Do this afterwards as the menu has now properly updated its size.
            for (int i = 0; i < items.Count; i++)
            {
                Add(new Coroutine(FadeIn(i, items[i])));
            }

            if (menu.Height > menu.ScrollableMinSize)
            {
                menu.Position.Y = menu.ScrollTargetY;
            }
        }
Esempio n. 6
0
        private void ReloadItems()
        {
            foreach (TextMenu.Item item in items)
            {
                menu.Remove(item);
            }
            items.Clear();

            string filterSet = null;

            if (type == 0)
            {
                filterSet = "Celeste";
            }
            else if (type >= 3)
            {
                filterSet = sets[type - 3];
            }

            string        lastLevelSet          = null;
            LevelSetStats levelSetStats         = null;
            int           levelSetAreaOffset    = 0;
            int           levelSetUnlockedAreas = int.MaxValue;
            int           levelSetUnlockedModes = int.MaxValue;
            string        name;

            SaveData         save         = SaveData.Instance;
            List <AreaStats> areaStatsAll = save.Areas;

            for (int i = 0; i < AreaData.Areas.Count; i++)
            {
                AreaData area = AreaData.Get(i);
                if (area == null || !area.HasMode((AreaMode)side))
                {
                    continue;
                }

                // TODO: Make subchapters hidden by default in the map list, even in debug mode.
                if (!save.DebugMode && !string.IsNullOrEmpty(area.GetMeta()?.Parent))
                {
                    continue;
                }

                string levelSet = area.GetLevelSet();

                if (type != 1 && ((filterSet == null && levelSet == "Celeste") || (filterSet != null && filterSet != levelSet)))
                {
                    continue;
                }

                name = area.Name;
                name = name.DialogCleanOrNull() ?? name.SpacedPascalCase();

                if (lastLevelSet != levelSet)
                {
                    lastLevelSet          = levelSet;
                    levelSetStats         = SaveData.Instance.GetLevelSetStatsFor(levelSet);
                    levelSetAreaOffset    = levelSetStats.AreaOffset;
                    levelSetUnlockedAreas = levelSetStats.UnlockedAreas;
                    levelSetUnlockedModes = levelSetStats.UnlockedModes;
                    string setname = DialogExt.CleanLevelSet(levelSet);
                    TextMenuExt.SubHeaderExt levelSetHeader = new TextMenuExt.SubHeaderExt(setname);
                    levelSetHeader.Alpha = 0f;
                    menu.Add(levelSetHeader);
                    items.Add(levelSetHeader);
                }

                TextMenuExt.ButtonExt button = new TextMenuExt.ButtonExt(name);
                button.Alpha = 0f;

                if (area.Icon != "areas/null")
                {
                    button.Icon = area.Icon;
                }
                button.IconWidth = 64f;

                if (levelSet == "Celeste" && i > levelSetAreaOffset + levelSetUnlockedAreas)
                {
                    button.Disabled = true;
                }
                if (side == 1 && !areaStatsAll[i].Cassette)
                {
                    button.Disabled = true;
                }
                if (side >= 2 && levelSetUnlockedModes < (side + 1))
                {
                    button.Disabled = true;
                }

                menu.Add(button.Pressed(() => {
                    Inspect(area, (AreaMode)side);
                }));
                items.Add(button);
            }

            // compute a delay so that options don't take more than a second to show up if many mods are installed.
            float delayBetweenOptions = 0.03f;

            if (items.Count > 0)
            {
                delayBetweenOptions = Math.Min(0.03f, 1f / items.Count);
            }

            // Do this afterwards as the menu has now properly updated its size.
            for (int i = 0; i < items.Count; i++)
            {
                Add(new Coroutine(FadeIn(i, delayBetweenOptions, items[i])));
            }

            if (menu.Height > menu.ScrollableMinSize)
            {
                menu.Position.Y = menu.ScrollTargetY;
            }
        }
Esempio n. 7
0
        private void ReloadItems()
        {
            foreach (TextMenu.Item item in items)
            {
                menu.Remove(item);
            }
            items.Clear();

            foreach (Everest.Updater.Source source in Everest.Updater.Sources)
            {
                TextMenuExt.SubHeaderExt header = new TextMenuExt.SubHeaderExt(source.NameDialog.DialogClean());
                header.Alpha = 0f;
                menu.Add(header);
                items.Add(header);

                if (source.ErrorDialog != null)
                {
                    string text = source.ErrorDialog.DialogClean();
                    TextMenuExt.SubHeaderExt error = new TextMenuExt.SubHeaderExt(text);
                    error.Alpha = 0f;
                    menu.Add(error);
                    items.Add(error);
                    continue;
                }

                if (source.Entries == null)
                {
                    TextMenuExt.SubHeaderExt info = new TextMenuExt.SubHeaderExt(Dialog.Clean("updater_versions_requesting"));
                    info.Alpha = 0f;
                    menu.Add(info);
                    items.Add(info);
                    continue;
                }

                string branch = null;
                int    count  = 0;
                foreach (Everest.Updater.Entry entry in source.Entries)
                {
                    if (entry.Branch != branch)
                    {
                        branch = entry.Branch;
                        count  = 0;

                        if (!string.IsNullOrEmpty(entry.Branch))
                        {
                            TextMenuExt.SubHeaderExt headerBranch = new TextMenuExt.SubHeaderExt("branch: " + entry.Branch);
                            headerBranch.Alpha = 0f;
                            menu.Add(headerBranch);
                            items.Add(headerBranch);
                        }
                    }
                    if (count >= buildsPerBranch)
                    {
                        continue;
                    }
                    count++;
                    TextMenuExt.ButtonExt item = new TextMenuExt.ButtonExt(entry.Name);
                    item.Alpha = 0f;
                    menu.Add(item.Pressed(() => {
                        Everest.Updater.Update(OuiModOptions.Instance.Overworld.Goto <OuiLoggedProgress>(), entry);
                    }));
                    items.Add(item);
                    continue;
                }
            }

            // Do this afterwards as the menu has now properly updated its size.
            for (int i = 0; i < items.Count; i++)
            {
                Add(new Coroutine(FadeIn(i, items[i])));
            }

            if (menu.Height > menu.ScrollableMinSize)
            {
                menu.Position.Y = menu.ScrollTargetY;
            }
        }
Esempio n. 8
0
        public override IEnumerator Enter(Oui from)
        {
            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;

            task = new Task(() => {
                try {
                    // 1. Download the updates list
                    string modUpdaterDatabaseUrl = getModUpdaterDatabaseUrl();

                    Logger.Log("OuiModUpdateList", $"Downloading last versions list from {modUpdaterDatabaseUrl}");

                    using (WebClient wc = new WebClient()) {
                        string yamlData = wc.DownloadString(modUpdaterDatabaseUrl);
                        updateCatalog   = new Deserializer().Deserialize <Dictionary <string, ModUpdateInfo> >(yamlData);
                        foreach (string name in updateCatalog.Keys)
                        {
                            updateCatalog[name].Name = name;
                        }
                        Logger.Log("OuiModUpdateList", $"Downloaded {updateCatalog.Count} item(s)");
                    }
                } catch (Exception e) {
                    Logger.Log("OuiModUpdateList", $"Downloading database failed!");
                    Logger.LogDetailed(e);
                }

                // 2. Find out what actually has been updated
                availableUpdatesCatalog.Clear();

                if (updateCatalog != null)
                {
                    Logger.Log("OuiModUpdateList", "Checking for updates");

                    foreach (EverestModule module in Everest.Modules)
                    {
                        EverestModuleMetadata metadata = module.Metadata;
                        if (metadata.PathArchive != null && updateCatalog.ContainsKey(metadata.Name))
                        {
                            string xxHashStringInstalled = BitConverter.ToString(metadata.Hash).Replace("-", "").ToLowerInvariant();
                            Logger.Log("OuiModUpdateList", $"Mod {metadata.Name}: installed hash {xxHashStringInstalled}, latest hash(es) {string.Join(", ", updateCatalog[metadata.Name].xxHash)}");
                            if (!updateCatalog[metadata.Name].xxHash.Contains(xxHashStringInstalled))
                            {
                                availableUpdatesCatalog.Add(updateCatalog[metadata.Name], metadata);
                            }
                        }
                    }

                    Logger.Log("OuiModUpdateList", $"{availableUpdatesCatalog.Count} update(s) available");
                }
            });

            task.Start();
        }