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. ------------------------------------ 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/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/InstallPackageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs new file mode 100644 index 00000000..149604c5 --- /dev/null +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/InstallPackageViewModel.cs @@ -0,0 +1,28 @@ +namespace Reloaded.Mod.Launcher.Lib.Models.ViewModel.Dialog; + +/// +/// ViewModel for downloading an individual package. +/// +public class InstallPackageViewModel : ObservableObject +{ + /// + /// 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; } + + /// + /// True if the installation is complete, else false. + /// + public bool IsComplete { get; set; } +} \ 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 5373ada0..71f58796 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/PublishModDialogViewModel.cs @@ -103,23 +103,12 @@ 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() - { - @".*\.json", // Config files - $"{Regex.Escape($@"{_modTuple.Config.ModId}.nuspec")}" - }; - - IncludeRegexes = new ObservableCollection() - { - Regex.Escape(ModConfig.ConfigFileName), - @"\.deps\.json", - @"\.runtimeconfig\.json", - }; - - // Set notifications - PropertyChanged += ChangeUiVisbilityOnPropertyChanged; + IgnoreRegexes = new ObservableCollection( + _modTuple.Config.IgnoreRegexes.Select(x => new StringWrapper { Value = x }) + ); + IncludeRegexes = new ObservableCollection( + _modTuple.Config.IncludeRegexes.Select(x => new StringWrapper { Value = x }) + ); } /// @@ -129,6 +118,9 @@ public PublishModDialogViewModel(PathTuple modTuple) /// 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 _)) { @@ -148,8 +140,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, @@ -286,6 +278,23 @@ public void SetOutputFolder() /// public void SetReadmePath() => ReadmePath = FileSelectors.SelectMarkdownFile(); + /// + /// Saves all changes to the mod config. + /// Automatically syncs collections before saving to ensure all in-memory edits are persisted. + /// + 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)!; @@ -296,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 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.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/Assets/Languages/en-GB.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml index bdce0f65..7318188a 100644 --- a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml +++ b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml @@ -197,6 +197,12 @@ 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/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..1bb80223 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); - /* Get name of package. */ - WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default); + var installVM = new InstallPackageViewModel + { + Title = InstallModArchiveTitle.Get(), + Text = ExtractingLocalModArchive.Get(), + Progress = 0 + }; + + var progress = new Progress(value => + { + 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) + }; + + timer.Tick += (s, e) => + { + timer.Stop(); + if (installVM.Progress != 100) + { + Actions.ShowInstallPackageDialog.Invoke(installVM); + } + }; + timer.Start(); + + await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, progress, default); + + installVM.Text = InstallingModWait.Get(); + 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..19230053 --- /dev/null +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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..3de571cb --- /dev/null +++ b/source/Reloaded.Mod.Launcher/Pages/Dialogs/InstallPackageDialog.xaml.cs @@ -0,0 +1,26 @@ +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; + DataContext = 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.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.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; 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 = "";