summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md4
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs57
-rw-r--r--src/SMAPI.Installer/Program.cs47
-rw-r--r--src/SMAPI.Installer/StardewModdingAPI.Installer.csproj6
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" />