diff options
-rw-r--r-- | docs/release-notes.md | 4 | ||||
-rw-r--r-- | src/SMAPI.Installer/InteractiveInstaller.cs | 57 | ||||
-rw-r--r-- | src/SMAPI.Installer/Program.cs | 47 | ||||
-rw-r--r-- | src/SMAPI.Installer/StardewModdingAPI.Installer.csproj | 6 |
4 files changed, 92 insertions, 22 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index 7a30b0b6..3ce7d6bb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,7 +3,8 @@ * For players: * Added support for subfolders under `Mods`, for players who want to organise their mods. * Moved most SMAPI files into a `smapi-internal` subfolder. - * Moved save backups into a `save-backups` subfolder (instead of `Mods/SaveBackup/backups`). + * Moved save backups into a `save-backups` subfolder (instead of `Mods/SaveBackup/backups`). Note that previous backups will be deleted when you update. + * Fixed installer duplicating bundled mods if you moved them after the last install. * Updated compatibility list. * For modders: @@ -15,6 +16,7 @@ * For SMAPI developers: * Adjusted `SaveBackup` mod to make it easier to account for custom mod subfolders in the installer. + * Installer no longer special-cases Omegasis' older `SaveBackup` mod (now named `AdvancedSaveBackup`). ## 2.7 * For players: diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index a92edadf..565ad732 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -10,6 +10,9 @@ using StardewModdingApi.Installer.Enums; using StardewModdingAPI.Installer.Framework; using StardewModdingAPI.Internal; using StardewModdingAPI.Internal.ConsoleWriting; +using StardewModdingAPI.Toolkit; +using StardewModdingAPI.Toolkit.Framework.ModScanning; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingApi.Installer { @@ -468,31 +471,45 @@ namespace StardewModdingApi.Installer { this.PrintDebug("Adding bundled mods..."); - // add bundled mods - foreach (DirectoryInfo sourceDir in packagedModsDir.EnumerateDirectories()) + ModToolkit toolkit = new ModToolkit(); + ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray(); + foreach (ModFolder sourceMod in toolkit.GetModFolders(packagedModsDir.FullName)) { - this.PrintDebug($" adding {sourceDir.Name}..."); - - // init/clear target dir - DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, sourceDir.Name)); - if (targetDir.Exists) - this.InteractivelyDelete(targetDir.FullName); - targetDir.Create(); + // validate source mod + if (sourceMod.Manifest == null) + { + this.PrintWarning($" ignored invalid bundled mod {sourceMod.DisplayName}: {sourceMod.ManifestParseError}"); + continue; + } + + // find target folder + ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase) == true); + DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); + DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; + this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName + ? $" adding {sourceMod.Manifest.Name}..." + : $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..." + ); + + // (re)create target folder + if (targetFolder.Exists) + this.InteractivelyDelete(targetFolder.FullName); + targetFolder.Create(); // copy files - foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopy)) - sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); + foreach (FileInfo sourceFile in sourceMod.Directory.EnumerateFiles().Where(this.ShouldCopy)) + sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name)); } + } - // set SMAPI's color scheme if defined - if (scheme != MonitorColorScheme.AutoDetect) - { - string configPath = Path.Combine(paths.GamePath, "StardewModdingAPI.config.json"); - string text = File - .ReadAllText(configPath) - .Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}"""); - File.WriteAllText(configPath, text); - } + // set SMAPI's color scheme if defined + if (scheme != MonitorColorScheme.AutoDetect) + { + string configPath = Path.Combine(paths.GamePath, "StardewModdingAPI.config.json"); + string text = File + .ReadAllText(configPath) + .Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}"""); + File.WriteAllText(configPath, text); } // remove obsolete appdata mods diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs index 8f328ecf..4d259fd3 100644 --- a/src/SMAPI.Installer/Program.cs +++ b/src/SMAPI.Installer/Program.cs @@ -1,17 +1,62 @@ -namespace StardewModdingApi.Installer +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; +using StardewModdingAPI.Internal; + +namespace StardewModdingApi.Installer { /// <summary>The entry point for SMAPI's install and uninstall console app.</summary> internal class Program { /********* + ** Properties + *********/ + /// <summary>The absolute path to search for referenced assemblies.</summary> + [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")] + private static string DllSearchPath; + + + /********* ** Public methods *********/ /// <summary>Run the install or uninstall script.</summary> /// <param name="args">The command line arguments.</param> public static void Main(string[] args) { + // set up assembly resolution + string installerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + Program.DllSearchPath = Path.Combine(installerPath, "internal", EnvironmentUtility.DetectPlatform() == Platform.Windows ? "Windows" : "Mono", "smapi-internal"); + AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; + + // launch installer var installer = new InteractiveInstaller(); installer.Run(args); } + + /********* + ** Private methods + *********/ + /// <summary>Method called when assembly resolution fails, which may return a manually resolved assembly.</summary> + /// <param name="sender">The event sender.</param> + /// <param name="e">The event arguments.</param> + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) + { + AssemblyName name = new AssemblyName(e.Name); + foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll")) + { + try + { + if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase)) + return Assembly.LoadFrom(dll.FullName); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Could not load dependency 'smapi-lib/{dll.Name}'. Consider deleting and redownloading the SMAPI installer.", ex); + } + } + + return null; + } } } diff --git a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj index e82c6093..e9af16c5 100644 --- a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj @@ -58,6 +58,12 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj"> + <Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project> + <Name>StardewModdingAPI.Toolkit</Name> + </ProjectReference> + </ItemGroup> <Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\build\common.targets" /> |