From bfd7cd0dac245c489e901994a2aaf77c85dbbb89 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Fri, 8 Aug 2025 00:22:06 +0530 Subject: [PATCH 01/15] Save included and excluded regex to modconfig for publishing --- .../Dialog/PublishModDialogViewModel.cs | 46 ++++++++++--------- .../Config/ModConfig.cs | 5 ++ 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 5373ada0..13704104 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -100,23 +100,17 @@ public class PublishModDialogViewModel : ObservableObject /// public PublishModDialogViewModel(PathTuple modTuple) { - _modTuple = modTuple; - PackageName = IOEx.ForceValidFilePath(_modTuple.Config.ModName.Replace(' ', '_')); - OutputFolder = Path.Combine(Path.GetTempPath(), $"{IOEx.ForceValidFilePath(_modTuple.Config.ModId)}.Publish"); - - // Set default Regexes. - IgnoreRegexes = new ObservableCollection() + _modTuple = modTuple; + if (!_modTuple.Config.IgnoreRegexes.Contains($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}")) { - @".*\.json", // Config files - $"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}" - }; - - IncludeRegexes = new ObservableCollection() + _modTuple.Config.IgnoreRegexes.Add($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}"); + } + if (!_modTuple.Config.IncludeRegexes.Contains(Regex.Escape(ModConfig.ConfigFileName))) { - Regex.Escape(ModConfig.ConfigFileName), - @"\.deps\.json", - @"\.runtimeconfig\.json", - }; + _modTuple.Config.IncludeRegexes.Add(Regex.Escape(ModConfig.ConfigFileName)); + } + PackageName = IOEx.ForceValidFilePath(_modTuple.Config.ModName.Replace(' ', '_')); + OutputFolder = Path.Combine(Path.GetTempPath(), $"{IOEx.ForceValidFilePath(_modTuple.Config.ModId)}.Publish"); // Set notifications PropertyChanged += ChangeUiVisbilityOnPropertyChanged; @@ -148,8 +142,8 @@ await PublishAsync(new PublishArgs() PublishTarget = PublishTarget, OutputFolder = OutputFolder, ModTuple = _modTuple, - IgnoreRegexes = IgnoreRegexes.Select(x => x.Value).ToList(), - IncludeRegexes = IncludeRegexes.Select(x => x.Value).ToList(), + IgnoreRegexes = _modTuple.Config.IgnoreRegexes, + IncludeRegexes = _modTuple.Config.IncludeRegexes, Progress = new Progress(d => BuildProgress = d * 100), AutomaticDelta = AutomaticDelta, CompressionLevel = CompressionLevel, @@ -227,22 +221,22 @@ public void AddNewVersionFolder() /// /// Removes the selected ignore regex. /// - public void RemoveSelectedIgnoreRegex() => RemoveSelectedOrLastItem(SelectedIgnoreRegex, IgnoreRegexes); + public void RemoveSelectedIgnoreRegex() => RemoveSelectedOrLastItemRegex(_modTuple.Config.IgnoreRegexes); /// /// Removes the selected include regex. /// - public void RemoveSelectedIncludeRegex() => RemoveSelectedOrLastItem(SelectedIncludeRegex, IncludeRegexes); + public void RemoveSelectedIncludeRegex() => RemoveSelectedOrLastItemRegex(_modTuple.Config.IncludeRegexes); /// /// Adds a regular expression for ignoring files. /// - public void AddIgnoreRegex() => IgnoreRegexes.Add("New Ignore Regex"); + public void AddIgnoreRegex() => _modTuple.Config.IgnoreRegexes.Add("New Ignore Regex"); /// /// Adds a regular expression for including files. /// - public void AddIncludeRegex() => IncludeRegexes.Add("New Include Regex"); + public void AddIncludeRegex() => _modTuple.Config.IncludeRegexes.Add("New Include Regex"); /// /// Calculates all files that will be removed from the final archive and displays them to the user. @@ -296,7 +290,15 @@ private void RemoveSelectedOrLastItem(StringWrapper? item, ObservableCollection< else if (allItems.Count > 0) allItems.RemoveAt(allItems.Count - 1); } - + + private void RemoveSelectedOrLastItemRegex(List allItems) + { + if (allItems.Count() > 0) + { + allItems.RemoveAt(allItems.Count - 1); + } + } + private void ChangeUiVisbilityOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(PublishTarget)) diff --git a/source/Reloaded.Mod.Loader.IO/Config/ModConfig.cs b/source/Reloaded.Mod.Loader.IO/Config/ModConfig.cs index b63e36b7..f583de42 100644 --- a/source/Reloaded.Mod.Loader.IO/Config/ModConfig.cs +++ b/source/Reloaded.Mod.Loader.IO/Config/ModConfig.cs @@ -1,4 +1,5 @@ using Reloaded.Memory.Extensions; +using System.Text.RegularExpressions; namespace Reloaded.Mod.Loader.IO.Config; @@ -36,6 +37,10 @@ public class ModConfig : ObservableObject, IConfig, IModConfig public bool IsLibrary { get; set; } = false; public string ReleaseMetadataFileName { get; set; } = "Sewer56.Update.ReleaseMetadata.json"; + /// Publishing + public List IgnoreRegexes { get; set; } = [@".*\.json"]; + public List IncludeRegexes { get; set; } = [@"\.deps\.json", @"\.runtimeconfig\.json"]; + [JsonIgnore] public string ModSubDirs { get; set; } = string.Empty; From 49b3e1e66b744fdef659d47501abf3c9d11fc727 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:40:37 +0530 Subject: [PATCH 02/15] Persist ignore/include regexes across sessions --- .../Dialog/PublishModDialogViewModel.cs | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 13704104..1c99c4fb 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -100,7 +100,9 @@ public class PublishModDialogViewModel : ObservableObject /// public PublishModDialogViewModel(PathTuple modTuple) { - _modTuple = modTuple; + _modTuple = modTuple; + PackageName = IOEx.ForceValidFilePath(_modTuple.Config.ModName.Replace(' ', '_')); + OutputFolder = Path.Combine(Path.GetTempPath(), $"{IOEx.ForceValidFilePath(_modTuple.Config.ModId)}.Publish"); if (!_modTuple.Config.IgnoreRegexes.Contains($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}")) { _modTuple.Config.IgnoreRegexes.Add($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}"); @@ -109,11 +111,27 @@ public PublishModDialogViewModel(PathTuple modTuple) { _modTuple.Config.IncludeRegexes.Add(Regex.Escape(ModConfig.ConfigFileName)); } - PackageName = IOEx.ForceValidFilePath(_modTuple.Config.ModName.Replace(' ', '_')); - OutputFolder = Path.Combine(Path.GetTempPath(), $"{IOEx.ForceValidFilePath(_modTuple.Config.ModId)}.Publish"); + IgnoreRegexes = new ObservableCollection( + _modTuple.Config.IgnoreRegexes.Select(x => new StringWrapper { Value = x }) + ); + IgnoreRegexes.CollectionChanged += (s, e) => + { + _modTuple.Config.IgnoreRegexes.Clear(); + _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); + _modTuple.Save(); + }; + + // IncludeRegexes + IncludeRegexes = new ObservableCollection( + _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) + ); + IncludeRegexes.CollectionChanged += (s, e) => + { + _modTuple.Config.IncludeRegexes.Clear(); + _modTuple.Config.IncludeRegexes.AddRange(IncludeRegexes.Select(x => x.Value)); + _modTuple.Save(); + }; - // Set notifications - PropertyChanged += ChangeUiVisbilityOnPropertyChanged; } /// @@ -221,22 +239,22 @@ public void AddNewVersionFolder() /// /// Removes the selected ignore regex. /// - public void RemoveSelectedIgnoreRegex() => RemoveSelectedOrLastItemRegex(_modTuple.Config.IgnoreRegexes); + public void RemoveSelectedIgnoreRegex() => RemoveSelectedOrLastItem(SelectedIgnoreRegex, IgnoreRegexes); /// /// Removes the selected include regex. /// - public void RemoveSelectedIncludeRegex() => RemoveSelectedOrLastItemRegex(_modTuple.Config.IncludeRegexes); + public void RemoveSelectedIncludeRegex() => RemoveSelectedOrLastItem(SelectedIncludeRegex, IncludeRegexes); /// /// Adds a regular expression for ignoring files. /// - public void AddIgnoreRegex() => _modTuple.Config.IgnoreRegexes.Add("New Ignore Regex"); + public void AddIgnoreRegex() => IgnoreRegexes.Add("New Ignore Regex"); /// /// Adds a regular expression for including files. /// - public void AddIncludeRegex() => _modTuple.Config.IncludeRegexes.Add("New Include Regex"); + public void AddIncludeRegex() => IncludeRegexes.Add("New Include Regex"); /// /// Calculates all files that will be removed from the final archive and displays them to the user. @@ -290,15 +308,7 @@ private void RemoveSelectedOrLastItem(StringWrapper? item, ObservableCollection< else if (allItems.Count > 0) allItems.RemoveAt(allItems.Count - 1); } - - private void RemoveSelectedOrLastItemRegex(List allItems) - { - if (allItems.Count() > 0) - { - allItems.RemoveAt(allItems.Count - 1); - } - } - + private void ChangeUiVisbilityOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(PublishTarget)) From 0cf29386872cd6386fa2318c97fc3c149861dbd2 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:46:17 +0530 Subject: [PATCH 03/15] Remove old comment --- .../Models/ViewModel/Dialog/PublishModDialogViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 1c99c4fb..5824407a 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -120,8 +120,6 @@ public PublishModDialogViewModel(PathTuple modTuple) _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); _modTuple.Save(); }; - - // IncludeRegexes IncludeRegexes = new ObservableCollection( _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) ); From bdff39682268b15ac7654de6419613b4c15f6558 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:16:00 +0530 Subject: [PATCH 04/15] Update PublishModDialogViewModel.cs --- .../Models/ViewModel/Dialog/PublishModDialogViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 5824407a..85d28b3c 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -118,7 +118,7 @@ public PublishModDialogViewModel(PathTuple modTuple) { _modTuple.Config.IgnoreRegexes.Clear(); _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); - _modTuple.Save(); + _modTuple.SaveAsync(); }; IncludeRegexes = new ObservableCollection( _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) @@ -127,7 +127,7 @@ public PublishModDialogViewModel(PathTuple modTuple) { _modTuple.Config.IncludeRegexes.Clear(); _modTuple.Config.IncludeRegexes.AddRange(IncludeRegexes.Select(x => x.Value)); - _modTuple.Save(); + _modTuple.SaveAsync(); }; } From f70880fa90fedaf85de52dc032a712d33348dd2c Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Sun, 10 Aug 2025 00:47:59 +0530 Subject: [PATCH 05/15] Added a progress bar for mod installation (large mods only), made CopyPackagesFromExtractFolderToTargetDir async to stop UI freezes for larger mods. --- source/Reloaded.Mod.Launcher.Lib/Lib.cs | 3 +- .../Dialog/InstallPackageViewModel.cs | 51 +++++++++++++++++++ .../Static/Actions.cs | 9 ++++ .../Assets/Languages/en-GB.xaml | 4 ++ .../Reloaded.Mod.Launcher/LibraryBindings.cs | 2 + .../Reloaded.Mod.Launcher/MainWindow.xaml.cs | 38 ++++++++++++-- .../Pages/Dialogs/InstallPackageDialog.xaml | 44 ++++++++++++++++ .../Dialogs/InstallPackageDialog.xaml.cs | 25 +++++++++ .../Update/UpdateDownloadablePackage.cs | 2 +- .../Providers/Web/WebDownloadablePackage.cs | 4 +- 10 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs create mode 100644 source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml create mode 100644 source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs diff --git a/source/Reloaded.Mod.Launcher.Lib/Lib.cs b/source/Reloaded.Mod.Launcher.Lib/Lib.cs index cd6255df..2194ed2e 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Lib.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Lib.cs @@ -62,7 +62,7 @@ public static void Init(IDictionaryResourceProvider provider, SynchronizationCon Actions.ShowModLoaderUpdateDialogDelegate showModLoaderUpdate, Actions.ShowModUpdateDialogDelegate showModUpdate, Actions.ConfigureNuGetFeedsDialogDelegate configureNuGetFeeds, Actions.ConfigureModDialogDelegate configureModDialog, Actions.ShowMissingCoreDependencyDialogDelegate showMissingCoreDependency, Actions.EditModDialogDelegate editModDialog, Actions.PublishModDialogDelegate publishModDialog, - Actions.ShowEditModUserConfigDialogDelegate showEditModUserConfig, Actions.ShowFetchPackageDialogDelegate showFetchPackageDialog, + Actions.ShowEditModUserConfigDialogDelegate showEditModUserConfig, Actions.ShowFetchPackageDialogDelegate showFetchPackageDialog, Actions.ShowInstallPackageDialogDelegate showInstallPackageDialog, Actions.ShowSelectAddedGameDialogDelegate showSelectAddedGameDialog, Actions.ShowAddAppHashMismatchDialogDelegate showAddAppMismatchDialog, Actions.ShowApplicationWarningDialogDelegate showApplicationWarningDialog, Actions.ShowRunAppViaWineDialogDelegate showRunAppViaWineDialog, Actions.ShowEditPackDialogDelegate showEditPackDialog, Actions.ShowInstallModPackDialogDelegate showInstallModPackDialog, Action initControllerSupport) @@ -85,6 +85,7 @@ public static void Init(IDictionaryResourceProvider provider, SynchronizationCon Actions.PublishModDialog = publishModDialog; Actions.ShowEditModUserConfig = showEditModUserConfig; Actions.ShowFetchPackageDialog = showFetchPackageDialog; + Actions.ShowInstallPackageDialog = showInstallPackageDialog; Actions.ShowSelectAddedGameDialog = showSelectAddedGameDialog; Actions.ShowAddAppHashMismatchDialog = showAddAppMismatchDialog; Actions.ShowApplicationWarningDialog = showApplicationWarningDialog; diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs new file mode 100644 index 00000000..3375f2b1 --- /dev/null +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs @@ -0,0 +1,51 @@ +namespace Reloaded.Mod.Launcher.Lib.Models.ViewModel.Dialog; + +/// +/// ViewModel for downloading an individual package. +/// +public class InstallPackageViewModel : INotifyPropertyChanged +{ + private string _text; + public string Text + { + get => _text; + set + { + if (_text != value) + { + _text = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text))); + } + } + } + + private double _progress; + public double Progress + { + get => _progress; + set + { + if (_progress != value) + { + _progress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress))); + } + } + } + + private bool _isComplete; + public bool IsComplete + { + get => _isComplete; + set + { + if (_isComplete != value) + { + _isComplete = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsComplete))); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; +} \ No newline at end of file diff --git a/source/Reloaded.Mod.Launcher.Lib/Static/Actions.cs b/source/Reloaded.Mod.Launcher.Lib/Static/Actions.cs index ca952f98..af850eb0 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Static/Actions.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Static/Actions.cs @@ -77,6 +77,9 @@ public static class Actions /// public static ShowFetchPackageDialogDelegate ShowFetchPackageDialog { get; set; } = null!; + public static ShowInstallPackageDialogDelegate ShowInstallPackageDialog { get; set; } = null!; + + /// /// Shows a dialog that can be used to select the added game. /// @@ -254,6 +257,12 @@ public enum MessageBoxType /// The ViewModel used for downloading the individual package. public delegate bool ShowFetchPackageDialogDelegate(DownloadPackageViewModel viewModel); + /// + /// Shows a dialog that can be used to download an individual package. + /// + /// The ViewModel used for downloading the individual package. + public delegate bool ShowInstallPackageDialogDelegate(InstallPackageViewModel viewModel); + /// /// Shows a dialog that can be used to select the added game. /// diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml index bdce0f65..9028169c 100644 --- a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml +++ b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml @@ -197,6 +197,10 @@ Download Mod Archive File Name + + + Installing Mod Archive + Mod Name You need to run this application as administrator. Administrative privileges are needed to receive application launch/exit events from Windows Management Instrumentation (WMI). Developers: Run your favourite IDE e.g. Visual Studio as Admin. diff --git a/source/Reloaded.Mod.Launcher/LibraryBindings.cs b/source/Reloaded.Mod.Launcher/LibraryBindings.cs index 2b623ef9..638db1b4 100644 --- a/source/Reloaded.Mod.Launcher/LibraryBindings.cs +++ b/source/Reloaded.Mod.Launcher/LibraryBindings.cs @@ -27,6 +27,7 @@ public static void Init(IResourceFileSelector? languageSelector, IResourceFileSe publishModDialog: PublishModDialog, showEditModUserConfig: ShowEditModUserConfig, showFetchPackageDialog: ShowFetchPackageDialog, + showInstallPackageDialog: ShowInstallPackageDialog, showSelectAddedGameDialog: ShowSelectAddedGameDialog, showAddAppMismatchDialog: ShowAddAppMismatchDialog, showApplicationWarningDialog: ShowApplicationWarningDialog, @@ -106,6 +107,7 @@ private static bool EditModDialog(EditModDialogViewModel viewmodel, object? owne private static bool ShowEditModUserConfig(EditModUserConfigDialogViewModel viewmodel) => ShowDialogAndGetResult(new EditModUserConfigDialog(viewmodel)); private static bool PublishModDialog(PublishModDialogViewModel viewmodel) => ShowDialogAndGetResult(new PublishModDialog(viewmodel)); private static bool ShowFetchPackageDialog(DownloadPackageViewModel viewmodel) => ShowDialogAndGetResult(new DownloadPackageDialog(viewmodel)); + private static bool ShowInstallPackageDialog(InstallPackageViewModel viewmodel) => ShowDialogAndGetResult(new InstallPackageDialog(viewmodel)); private static bool ShowAddAppMismatchDialog(AddAppHashMismatchDialogViewModel viewmodel) => ShowDialogAndGetResult(new AddAppHashMismatchDialog(viewmodel)); private static bool ShowApplicationWarningDialog(AddApplicationWarningDialogViewModel viewmodel) => ShowDialogAndGetResult(new ShowApplicationWarningDialog(viewmodel)); private static bool ShowInstallModPackDialog(InstallModPackDialogViewModel viewmodel) => ShowDialogAndGetResult(new InstallModPackDialog(viewmodel)); diff --git a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs index eb9c0816..4db3ed38 100644 --- a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs +++ b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs @@ -1,7 +1,9 @@ -using System.Text; +using NuGet.Common; using Reloaded.Mod.Loader.Update.Providers.Web; using Sewer56.DeltaPatchGenerator.Lib.Utility; using Sewer56.Update.Extractors.SevenZipSharp; +using System.Text; +using System.Windows.Threading; using static Reloaded.Mod.Launcher.Lib.Static.Resources; namespace Reloaded.Mod.Launcher; @@ -88,10 +90,40 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) /* Extract to Temp Directory */ using var tempFolder = new TemporaryFolderAllocation(); var archiveExtractor = new SevenZipSharpExtractor(); - await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, new Progress(), default); + + var installVM = new InstallPackageViewModel + { + Text = "Extracting a local mod, please wait!", + Progress = 0 + }; + + var progress = new Progress(value => + { + installVM.Progress = value * 100; + }); + + var timer = new DispatcherTimer + { + Interval = TimeSpan.FromSeconds(3) + }; + + timer.Tick += (s, e) => + { + timer.Stop(); + if (installVM.Progress != 100) + { + Actions.ShowInstallPackageDialog.Invoke(installVM); + } + }; + + timer.Start(); + + await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, progress, default); /* Get name of package. */ - WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default); + installVM.Text = "Please wait while we install the mod!"; + await WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default); + installVM.IsComplete = true; } // Find the new mods diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml new file mode 100644 index 00000000..7e4a2b3d --- /dev/null +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs new file mode 100644 index 00000000..232b745f --- /dev/null +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs @@ -0,0 +1,25 @@ +using Button = System.Windows.Controls.Button; + +namespace Reloaded.Mod.Launcher.Pages.Dialogs; + +/// +/// Interaction logic for DownloadPackageDialog.xaml +/// +public partial class InstallPackageDialog : ReloadedWindow +{ + public new InstallPackageViewModel ViewModel { get; set; } + + /// + public InstallPackageDialog(InstallPackageViewModel viewModel) + { + InitializeComponent(); + ViewModel = viewModel; + viewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(InstallPackageViewModel.IsComplete) && viewModel.IsComplete) + { + ActionWrappers.ExecuteWithApplicationDispatcher(this.Close); + } + }; + } +} \ No newline at end of file diff --git a/source/Reloaded.Mod.Loader.Update/Providers/Update/UpdateDownloadablePackage.cs b/source/Reloaded.Mod.Loader.Update/Providers/Update/UpdateDownloadablePackage.cs index 17a743ea..e22b2013 100644 --- a/source/Reloaded.Mod.Loader.Update/Providers/Update/UpdateDownloadablePackage.cs +++ b/source/Reloaded.Mod.Loader.Update/Providers/Update/UpdateDownloadablePackage.cs @@ -116,7 +116,7 @@ await retryPolicy.ExecuteAsync(async () => await archiveExtractor.ExtractPackageAsync(tempDownloadPath, tempExtractDir.FolderPath, extractSlice, token); // Copy all packages from download. - return WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(packageFolder, tempExtractDir.FolderPath, token); + return await WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(packageFolder, tempExtractDir.FolderPath, token); } #pragma warning disable CS0067 // Event never used diff --git a/source/Reloaded.Mod.Loader.Update/Providers/Web/WebDownloadablePackage.cs b/source/Reloaded.Mod.Loader.Update/Providers/Web/WebDownloadablePackage.cs index 679ab373..d254934b 100644 --- a/source/Reloaded.Mod.Loader.Update/Providers/Web/WebDownloadablePackage.cs +++ b/source/Reloaded.Mod.Loader.Update/Providers/Web/WebDownloadablePackage.cs @@ -129,7 +129,7 @@ await retryPolicy.ExecuteAsync(async () => await archiveExtractor.ExtractPackageAsync(tempFilePath, tempExtractDirectory.FolderPath, extractProgress, token); /* Get name of package. */ - return CopyPackagesFromExtractFolderToTargetDir(packageFolder, tempExtractDirectory.FolderPath, token); + return await CopyPackagesFromExtractFolderToTargetDir(packageFolder, tempExtractDirectory.FolderPath, token); } /// @@ -139,7 +139,7 @@ await retryPolicy.ExecuteAsync(async () => /// Finds all mods in and copies them to appropriate subfolders in . /// /// Path to last folder copied. - public static string CopyPackagesFromExtractFolderToTargetDir(string packageFolder, string tempExtractDir, CancellationToken token) + public async static Task CopyPackagesFromExtractFolderToTargetDir(string packageFolder, string tempExtractDir, CancellationToken token) { var configs = ConfigReader.ReadConfigurations(tempExtractDir, ModConfig.ConfigFileName, token, int.MaxValue, 0); var returnResult = ""; From 0966c60cca812d72397b5217fe5d1262f1e8ceb5 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:50:45 +0530 Subject: [PATCH 06/15] Fixed bug where the config was not updated if the value in the StringWrapper was modified The config used to only be updated once an item was added or deleted. --- .../Dialog/PublishModDialogViewModel.cs | 26 +++++++++++++++++++ .../Utilities/StringWrapper.cs | 14 +++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 85d28b3c..69f226f7 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -106,16 +106,27 @@ public PublishModDialogViewModel(PathTuple modTuple) if (!_modTuple.Config.IgnoreRegexes.Contains($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}")) { _modTuple.Config.IgnoreRegexes.Add($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}"); + _modTuple.SaveAsync(); } if (!_modTuple.Config.IncludeRegexes.Contains(Regex.Escape(ModConfig.ConfigFileName))) { _modTuple.Config.IncludeRegexes.Add(Regex.Escape(ModConfig.ConfigFileName)); + _modTuple.SaveAsync(); } IgnoreRegexes = new ObservableCollection( _modTuple.Config.IgnoreRegexes.Select(x => new StringWrapper { Value = x }) ); + foreach (StringWrapper item in IgnoreRegexes) + item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); IgnoreRegexes.CollectionChanged += (s, e) => { + if (e.NewItems != null) + foreach (StringWrapper item in e.NewItems) + item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); + + if (e.OldItems != null) + foreach (StringWrapper item in e.OldItems) + item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); _modTuple.Config.IgnoreRegexes.Clear(); _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); _modTuple.SaveAsync(); @@ -123,13 +134,28 @@ public PublishModDialogViewModel(PathTuple modTuple) IncludeRegexes = new ObservableCollection( _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) ); + foreach (StringWrapper item in IncludeRegexes) + item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); IncludeRegexes.CollectionChanged += (s, e) => { + if (e.NewItems != null) + foreach (StringWrapper item in e.NewItems) + item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); + + if (e.OldItems != null) + foreach (StringWrapper item in e.OldItems) + item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); _modTuple.Config.IncludeRegexes.Clear(); _modTuple.Config.IncludeRegexes.AddRange(IncludeRegexes.Select(x => x.Value)); _modTuple.SaveAsync(); }; + } + void UpdateConfig(List list, ObservableCollection collection) + { + list.Clear(); + list.AddRange(collection.Select(x => x.Value)); + _modTuple.SaveAsync(); } /// diff --git a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs index 65689ee8..30b2c342 100644 --- a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs +++ b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs @@ -6,10 +6,22 @@ namespace Reloaded.Mod.Loader.Update.Utilities; [JsonConverter(typeof(StringWrapperConverter))] public class StringWrapper : ObservableObject { + private string _value = ""; /// /// Value of the string wrapper. /// - public string Value { get; set; } = ""; + public string Value + { + get => _value; + set + { + if (_value != value) + { + _value = value; + RaisePropertyChangedEvent(nameof(Value)); + } + } + } /// public static implicit operator string(StringWrapper wrapper) => wrapper.Value; From a00d75103aec6f8fe3c995c851c689073864f2e6 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:53:13 +0530 Subject: [PATCH 07/15] Make suggested changes --- source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml | 4 +++- source/Reloaded.Mod.Launcher/MainWindow.xaml.cs | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml index 9028169c..7318188a 100644 --- a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml +++ b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml @@ -198,9 +198,11 @@ Download Mod Archive File Name - + Installing Mod Archive Mod Name + Please wait while we install the mod! + Extracting a local mod, please wait! You need to run this application as administrator. Administrative privileges are needed to receive application launch/exit events from Windows Management Instrumentation (WMI). Developers: Run your favourite IDE e.g. Visual Studio as Admin. diff --git a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs index 4db3ed38..6b2c6e90 100644 --- a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs +++ b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs @@ -93,7 +93,7 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) var installVM = new InstallPackageViewModel { - Text = "Extracting a local mod, please wait!", + Text = (string)Application.Current.Resources["ExtractingLocalModArchive"], Progress = 0 }; @@ -115,13 +115,11 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) Actions.ShowInstallPackageDialog.Invoke(installVM); } }; - timer.Start(); await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, progress, default); - /* Get name of package. */ - installVM.Text = "Please wait while we install the mod!"; + installVM.Text = (string)Application.Current.Resources["InstallingModWait"]; await WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default); installVM.IsComplete = true; } From e8e5498a39d7a0485724e3f6f6c139a3251bcd7e Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:05:11 +0530 Subject: [PATCH 08/15] Only add default regexes when creating the mod Makes it so they don't come back everytime you open the publish window if you remove them (though I have no idea why anyone would remove these regexes) --- .../Models/ViewModel/Dialog/CreateModViewModel.cs | 2 ++ .../ViewModel/Dialog/PublishModDialogViewModel.cs | 10 ---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/CreateModViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/CreateModViewModel.cs index 4ed5187d..2f239cdb 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/CreateModViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/CreateModViewModel.cs @@ -33,6 +33,8 @@ public CreateModViewModel(ModConfigService modConfigService) ReleaseMetadataFileName = $"{ModId}.ReleaseMetadata.json" }; + config.IgnoreRegexes.Add($"{Regex.Escape($@"{config.ModId}.nuspec")}"); + config.IncludeRegexes.Add(Regex.Escape(ModConfig.ConfigFileName)); var modDirectory = Path.Combine(IoC.Get().GetModConfigDirectory(), IOEx.ForceValidFilePath(ModId)); var filePath = Path.Combine(modDirectory, ModConfig.ConfigFileName); await IConfig.ToPathAsync(config, filePath); diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index 69f226f7..ff1c8f60 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -103,16 +103,6 @@ public PublishModDialogViewModel(PathTuple modTuple) _modTuple = modTuple; PackageName = IOEx.ForceValidFilePath(_modTuple.Config.ModName.Replace(' ', '_')); OutputFolder = Path.Combine(Path.GetTempPath(), $"{IOEx.ForceValidFilePath(_modTuple.Config.ModId)}.Publish"); - if (!_modTuple.Config.IgnoreRegexes.Contains($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}")) - { - _modTuple.Config.IgnoreRegexes.Add($"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}"); - _modTuple.SaveAsync(); - } - if (!_modTuple.Config.IncludeRegexes.Contains(Regex.Escape(ModConfig.ConfigFileName))) - { - _modTuple.Config.IncludeRegexes.Add(Regex.Escape(ModConfig.ConfigFileName)); - _modTuple.SaveAsync(); - } IgnoreRegexes = new ObservableCollection( _modTuple.Config.IgnoreRegexes.Select(x => new StringWrapper { Value = x }) ); From 1f7c0613e8a12e81e3b9d13951ee116a06afa1f5 Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:20:28 +0530 Subject: [PATCH 09/15] Make suggested changes again --- .../ViewModel/Dialog/InstallPackageViewModel.cs | 14 ++++++++++++++ .../Reloaded.Mod.Launcher.Lib/Static/Resources.cs | 8 +++++++- source/Reloaded.Mod.Launcher/MainWindow.xaml.cs | 5 +++-- .../Pages/Dialogs/InstallPackageDialog.xaml | 2 +- .../Pages/Dialogs/InstallPackageDialog.xaml.cs | 1 + 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs index 3375f2b1..cad23f70 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs @@ -19,6 +19,20 @@ public string Text } } + private string _title; + public string Title + { + get => _title; + set + { + if (_title != value) + { + _title = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title))); + } + } + } + private double _progress; public double Progress { diff --git a/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs b/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs index 1b329244..00014d0b 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs @@ -181,7 +181,7 @@ public static void Init(IDictionaryResourceProvider provider) // Update 1.21.0: Mod Packs Install public static IDictionaryResource InstallModPackDownloading { get; set; } public static IDictionaryResource InstallModPackErrorDownloadFail { get; set; } - + // Update 1.21.6: Mod Packs Install public static IDictionaryResource ErrorAddApplicationGeneral { get; set; } public static IDictionaryResource ErrorAddApplicationCantReadSymlink { get; set; } @@ -219,4 +219,10 @@ public static void Init(IDictionaryResourceProvider provider) public static IDictionaryResource ErrorViewDetails { get; set; } public static IDictionaryResource ErrorStacktraceTitle { get; set; } public static IDictionaryResource ErrorStacktraceSubtitle { get; set; } + + // Update 1.X.X: New Progress Window for Local Mod Installation (UPDATE LAUNCHER VER BEFORE RELEASE) + public static IDictionaryResource InstallModArchiveTitle { get; set; } + public static IDictionaryResource InstalledModName { get; set; } + public static IDictionaryResource InstallingModWait { get; set; } + public static IDictionaryResource ExtractingLocalModArchive { get; set; } } \ No newline at end of file diff --git a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs index 6b2c6e90..ff29f00f 100644 --- a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs +++ b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs @@ -93,7 +93,8 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) var installVM = new InstallPackageViewModel { - Text = (string)Application.Current.Resources["ExtractingLocalModArchive"], + Title = InstallModArchiveTitle.Get(), + Text = ExtractingLocalModArchive.Get(), Progress = 0 }; @@ -119,7 +120,7 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, progress, default); - installVM.Text = (string)Application.Current.Resources["InstallingModWait"]; + installVM.Text = InstallingModWait.Get(); await WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default); installVM.IsComplete = true; } diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml index 7e4a2b3d..f9cb59b1 100644 --- a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml @@ -9,7 +9,7 @@ mc:Ignorable="d" SizeToContent="Height" Width="500" - Title="{DynamicResource InstallModArchiveTitle}" + Title="{Binding Title}" Style="{DynamicResource ReloadedWindow}"> diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs index 232b745f..3de571cb 100644 --- a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs @@ -14,6 +14,7 @@ public InstallPackageDialog(InstallPackageViewModel viewModel) { InitializeComponent(); ViewModel = viewModel; + DataContext = viewModel; viewModel.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(InstallPackageViewModel.IsComplete) && viewModel.IsComplete) From 626aceff2ccf6494ee41a593ac278dec4d138b5a Mon Sep 17 00:00:00 2001 From: TheBestAstroNOT <139786546+TheBestAstroNOT@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:43:53 +0530 Subject: [PATCH 10/15] Make suggested changes again again Sorry for the bad commit names, it's a bit late for me rn --- .../Dialog/InstallPackageViewModel.cs | 60 ++----------------- .../Dialog/PublishModDialogViewModel.cs | 7 ++- .../Reloaded.Mod.Launcher/MainWindow.xaml.cs | 1 + .../Pages/Dialogs/PublishModDialog.xaml.cs | 5 +- .../Utilities/StringWrapper.cs | 17 ++---- 5 files changed, 19 insertions(+), 71 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs index cad23f70..dbc6bd12 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs @@ -3,63 +3,13 @@ namespace Reloaded.Mod.Launcher.Lib.Models.ViewModel.Dialog; /// /// ViewModel for downloading an individual package. /// +[AddINotifyPropertyChangedInterface] public class InstallPackageViewModel : INotifyPropertyChanged { - private string _text; - public string Text - { - get => _text; - set - { - if (_text != value) - { - _text = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text))); - } - } - } - - private string _title; - public string Title - { - get => _title; - set - { - if (_title != value) - { - _title = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title))); - } - } - } - - private double _progress; - public double Progress - { - get => _progress; - set - { - if (_progress != value) - { - _progress = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress))); - } - } - } - - private bool _isComplete; - public bool IsComplete - { - get => _isComplete; - set - { - if (_isComplete != value) - { - _isComplete = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsComplete))); - } - } - } + public string Text { get; set; } + public string Title { get; set; } + public double Progress { get; set; } + public bool IsComplete { get; set; } public event PropertyChangedEventHandler PropertyChanged; } \ No newline at end of file diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index ff1c8f60..e19e7c94 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -119,7 +119,6 @@ public PublishModDialogViewModel(PathTuple modTuple) item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); _modTuple.Config.IgnoreRegexes.Clear(); _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); - _modTuple.SaveAsync(); }; IncludeRegexes = new ObservableCollection( _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) @@ -137,7 +136,6 @@ public PublishModDialogViewModel(PathTuple modTuple) item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); _modTuple.Config.IncludeRegexes.Clear(); _modTuple.Config.IncludeRegexes.AddRange(IncludeRegexes.Select(x => x.Value)); - _modTuple.SaveAsync(); }; } @@ -312,6 +310,11 @@ public void SetOutputFolder() /// public void SetReadmePath() => ReadmePath = FileSelectors.SelectMarkdownFile(); + /// + /// Lets the user save all changes to the mod config. + /// + public Task SaveAsync() => _modTuple.SaveAsync(); + private string GetModFolder() => Path.GetDirectoryName(_modTuple.Path)!; diff --git a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs index ff29f00f..1bb80223 100644 --- a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs +++ b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs @@ -103,6 +103,7 @@ private async void InstallMod_Drop(object sender, DragEventArgs e) installVM.Progress = value * 100; }); + //Waits for 3 seconds before showing the install dialog, if the installation is not complete by then. var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/PublishModDialog.xaml.cs b/source/Reloaded.Mod.Launcher/Pages/Dialogs/PublishModDialog.xaml.cs index 6901ade2..235ac004 100644 --- a/source/Reloaded.Mod.Launcher/Pages/Dialogs/PublishModDialog.xaml.cs +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/PublishModDialog.xaml.cs @@ -20,7 +20,10 @@ public PublishModDialog(PublishModDialogViewModel viewModel) this.Closing += OnClosing; } - private void OnClosing(object? sender, CancelEventArgs e) => _cancellationTokenSource.Cancel(); + private async void OnClosing(object? sender, CancelEventArgs e) { + await ViewModel.SaveAsync(); + _cancellationTokenSource.Cancel(); + } private async void Publish_Click(object sender, System.Windows.RoutedEventArgs e) { diff --git a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs index 30b2c342..8d2c2331 100644 --- a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs +++ b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs @@ -1,27 +1,18 @@ +using PropertyChanged; + namespace Reloaded.Mod.Loader.Update.Utilities; /// /// Class that wraps a string. Used for data binding. /// +[AddINotifyPropertyChangedInterface] [JsonConverter(typeof(StringWrapperConverter))] public class StringWrapper : ObservableObject { - private string _value = ""; /// /// Value of the string wrapper. /// - public string Value - { - get => _value; - set - { - if (_value != value) - { - _value = value; - RaisePropertyChangedEvent(nameof(Value)); - } - } - } + public string Value { get; set; } = ""; /// public static implicit operator string(StringWrapper wrapper) => wrapper.Value; From fbc6dd0d077b3a9233f4ac13fd9441c224235146 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sat, 15 Nov 2025 01:23:52 +0000 Subject: [PATCH 11/15] Changed: Follow Fody Weaving Pattern from rest of source. --- .../Dialog/InstallPackageViewModel.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs index dbc6bd12..149604c5 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs @@ -3,13 +3,26 @@ namespace Reloaded.Mod.Launcher.Lib.Models.ViewModel.Dialog; /// /// ViewModel for downloading an individual package. /// -[AddINotifyPropertyChangedInterface] -public class InstallPackageViewModel : INotifyPropertyChanged +public class InstallPackageViewModel : ObservableObject { - public string Text { get; set; } - public string Title { get; set; } + /// + /// The display text for the package installation. + /// + public string Text { get; set; } = ""; + + /// + /// The title for the package installation. + /// + public string Title { get; set; } = ""; + + /// + /// The current progress of the installation operation. + /// Range 0-100. + /// public double Progress { get; set; } - public bool IsComplete { get; set; } - public event PropertyChangedEventHandler PropertyChanged; + /// + /// True if the installation is complete, else false. + /// + public bool IsComplete { get; set; } } \ No newline at end of file From 53be43b1eb9710fac9012b90089201222cf52384 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sat, 15 Nov 2025 01:43:49 +0000 Subject: [PATCH 12/15] Improve: Center InstallPackageDialog to the screen --- .../Pages/Dialogs/InstallPackageDialog.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml index f9cb59b1..19230053 100644 --- a/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml @@ -1,4 +1,4 @@ - From e3f34998916ed8222c65c10affaec4880b2af9bf Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sat, 15 Nov 2025 01:49:45 +0000 Subject: [PATCH 13/15] Removed: Redundant AddINotifyPropertyChangedInterface from StringWrapper --- source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs index 8d2c2331..65689ee8 100644 --- a/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs +++ b/source/Reloaded.Mod.Loader.Update/Utilities/StringWrapper.cs @@ -1,11 +1,8 @@ -using PropertyChanged; - namespace Reloaded.Mod.Loader.Update.Utilities; /// /// Class that wraps a string. Used for data binding. /// -[AddINotifyPropertyChangedInterface] [JsonConverter(typeof(StringWrapperConverter))] public class StringWrapper : ObservableObject { From 966abe1c936c280e4384dea0cedf7bb9eb02f3e4 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sat, 15 Nov 2025 02:18:41 +0000 Subject: [PATCH 14/15] Changed: Updated IgnoreRegexes & IncludeRegexes to only update on save in publish menu. Not in real time, too expensive. --- .../Dialog/PublishModDialogViewModel.cs | 60 ++++++------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs index e19e7c94..71f58796 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -106,44 +106,9 @@ public PublishModDialogViewModel(PathTuple modTuple) IgnoreRegexes = new ObservableCollection( _modTuple.Config.IgnoreRegexes.Select(x => new StringWrapper { Value = x }) ); - foreach (StringWrapper item in IgnoreRegexes) - item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); - IgnoreRegexes.CollectionChanged += (s, e) => - { - if (e.NewItems != null) - foreach (StringWrapper item in e.NewItems) - item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); - - if (e.OldItems != null) - foreach (StringWrapper item in e.OldItems) - item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); - _modTuple.Config.IgnoreRegexes.Clear(); - _modTuple.Config.IgnoreRegexes.AddRange(IgnoreRegexes.Select(x => x.Value)); - }; IncludeRegexes = new ObservableCollection( _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) ); - foreach (StringWrapper item in IncludeRegexes) - item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); - IncludeRegexes.CollectionChanged += (s, e) => - { - if (e.NewItems != null) - foreach (StringWrapper item in e.NewItems) - item.PropertyChanged += (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); - - if (e.OldItems != null) - foreach (StringWrapper item in e.OldItems) - item.PropertyChanged -= (_, __) => UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); - _modTuple.Config.IncludeRegexes.Clear(); - _modTuple.Config.IncludeRegexes.AddRange(IncludeRegexes.Select(x => x.Value)); - }; - } - - void UpdateConfig(List list, ObservableCollection collection) - { - list.Clear(); - list.AddRange(collection.Select(x => x.Value)); - _modTuple.SaveAsync(); } /// @@ -153,6 +118,9 @@ void UpdateConfig(List list, ObservableCollection collect /// True if a build has started and the operation completed, else false. public async Task BuildAsync(CancellationToken cancellationToken = default) { + // Save all changes before building to ensure config is current + await SaveAsync(); + // Check if Auto Delta can be performed. if (AutomaticDelta && !Singleton.Instance.CanReadFromDirectory(OutputFolder, null, out _, out _)) { @@ -311,9 +279,21 @@ public void SetOutputFolder() public void SetReadmePath() => ReadmePath = FileSelectors.SelectMarkdownFile(); /// - /// Lets the user save all changes to the mod config. + /// Saves all changes to the mod config. + /// Automatically syncs collections before saving to ensure all in-memory edits are persisted. /// - public Task SaveAsync() => _modTuple.SaveAsync(); + public async Task SaveAsync() + { + UpdateConfig(_modTuple.Config.IgnoreRegexes, IgnoreRegexes); + UpdateConfig(_modTuple.Config.IncludeRegexes, IncludeRegexes); + await _modTuple.SaveAsync(); + + void UpdateConfig(List list, ObservableCollection collection) + { + list.Clear(); + list.AddRange(collection.Select(x => x.Value)); + } + } private string GetModFolder() => Path.GetDirectoryName(_modTuple.Path)!; @@ -325,10 +305,4 @@ private void RemoveSelectedOrLastItem(StringWrapper? item, ObservableCollection< else if (allItems.Count > 0) allItems.RemoveAt(allItems.Count - 1); } - - private void ChangeUiVisbilityOnPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(PublishTarget)) - ShowLastVersionUiItems = PublishTarget != PublishTarget.NuGet; - } } \ No newline at end of file From 78a2e013825cb335216daea29916447a78420c7d Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Sat, 15 Nov 2025 23:40:31 +0000 Subject: [PATCH 15/15] Updated: Template of Changelog Update(s) --- changelog-template.hbs | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/changelog-template.hbs b/changelog-template.hbs index c3cb6790..cde787d3 100644 --- a/changelog-template.hbs +++ b/changelog-template.hbs @@ -1,34 +1,29 @@ [Read and Discuss in a Browser](https://github.com/Reloaded-Project/Reloaded-II/discussions/473). -[Previous Changelog](https://github.com/Reloaded-Project/Reloaded-II/releases/tag/1.29.1). - -# 1.29.2: Miscellaneous Things +[Previous Changelog](https://github.com/Reloaded-Project/Reloaded-II/releases/tag/1.29.2). Just a few low effort fixes and miscellany. As usual, Reloaded-II is on life support while I spend the next years building the next best thing - if you want features, please contribute! -## Fix: Better Error Handling for Bad Update Files - -Previously, if someone uploaded a corrupted `Sewer56.Update.Metadata.json` file to GameBanana or GitHub, -Reloaded would crash. Now it handles these errors gracefully instead of crashing. +# vNext: Miscellaneous Improvements -Might add UI in future. +No update to .NET 10 as of current. -## Fix: Unnecessary Runtime Downloads +According to 3rd party reports I got thus far, `Proton` needs updating to receive some upstream fixes from `Wine` for .NET 10, which may take a while. -![](https://github.com/user-attachments/assets/45795e1b-c922-44e9-9134-60fe18b27f29) +## Added Progress Bar for Mod Installation -Fixed an issue where the dependency installer was requesting unnecessary .NET runtime downloads. -The problem was that the installer wasn't filtering out the `Mods` folder, so it tried to install -runtimes that mods were built with, even though all mods use the loader's runtime. +![Image](https://github.com/user-attachments/assets/9f09ccad-f036-4856-806c-3c75f281307a) -Now it only installs truly needed dependencies. +If a mod takes longer than 3 seconds to install via Drag & Drop, a simple progress bar will display on the screen now. +In practice you should only see this if you install large mods (think >500MB); on a typical CPU. -[This happened because in the last release the in-launcher dependency installer was updated to use -the same code as `Setup.exe`; and this was a small oversight in the migration.] +## Save Mod Config on Publish, Including `IncludeFiles` and `ExcludeFiles` by @TheBestAstroNOT , @Sewer56 -## Updated French Localization by @dysfunctionalriot +When you `Publish` a mod you have an option to exclude certain files from the released project via `File Inclusions & Exclusions`. +By default, that is all `.json` files except `.deps.json` and `.runtimeconfig.json` ones. -🇫🇷 Updated translations for version 1.29.1. +Previously, changes on this menu didn't save; as barely anyone ever changed the defaults. +Now they do. ------------------------------------