diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Installer/InteractiveInstaller.cs | 115 | ||||
-rw-r--r-- | src/SMAPI.ModBuildConfig/build/smapi.targets | 2 | ||||
-rw-r--r-- | src/SMAPI.ModBuildConfig/package.nuspec | 5 | ||||
-rw-r--r-- | src/SMAPI/Constants.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 1 | ||||
-rw-r--r-- | src/SMAPI/Program.cs | 85 |
6 files changed, 139 insertions, 76 deletions
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 0aac1da2..f9e1ff94 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -93,40 +93,39 @@ namespace StardewModdingApi.Installer { string GetInstallPath(string path) => Path.Combine(installDir.FullName, path); - // common - yield return GetInstallPath("0Harmony.dll"); - yield return GetInstallPath("0Harmony.pdb"); - yield return GetInstallPath("Mono.Cecil.dll"); - yield return GetInstallPath("Newtonsoft.Json.dll"); + // current files + yield return GetInstallPath("libgdiplus.dylib"); // Linux/Mac only + yield return GetInstallPath("StardewModdingAPI"); // Linux/Mac only yield return GetInstallPath("StardewModdingAPI.exe"); - yield return GetInstallPath("StardewModdingAPI.config.json"); - yield return GetInstallPath("StardewModdingAPI.metadata.json"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.dll"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.pdb"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.xml"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); - yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); + yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/Mac only + yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only yield return GetInstallPath("StardewModdingAPI.xml"); - yield return GetInstallPath("System.ValueTuple.dll"); - yield return GetInstallPath("steam_appid.txt"); - - // Linux/Mac only - yield return GetInstallPath("libgdiplus.dylib"); - yield return GetInstallPath("StardewModdingAPI"); - yield return GetInstallPath("StardewModdingAPI.exe.mdb"); - yield return GetInstallPath("System.Numerics.dll"); - yield return GetInstallPath("System.Runtime.Caching.dll"); - - // Windows only - yield return GetInstallPath("StardewModdingAPI.pdb"); + yield return GetInstallPath("smapi-internal"); // obsolete - yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4 + yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4 yield return GetInstallPath(Path.Combine("Mods", "TrainerMod")); // *–2.0 (renamed to ConsoleCommands) - yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8 - yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4 + yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8 + yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4 yield return GetInstallPath("StardewModdingAPI.AssemblyRewriters.dll"); // 1.3-2.5.5 + yield return GetInstallPath("0Harmony.dll"); // moved in 2.8 + yield return GetInstallPath("0Harmony.pdb"); // moved in 2.8 + yield return GetInstallPath("Mono.Cecil.dll"); // moved in 2.8 + yield return GetInstallPath("Newtonsoft.Json.dll"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.config.json"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.metadata.json"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.dll"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.pdb"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.xml"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); // moved in 2.8 + yield return GetInstallPath("StardewModdingAPI.xml"); // moved in 2.8 + yield return GetInstallPath("System.Numerics.dll"); // moved in 2.8 + yield return GetInstallPath("System.Runtime.Caching.dll"); // moved in 2.8 + yield return GetInstallPath("System.ValueTuple.dll"); // moved in 2.8 + yield return GetInstallPath("steam_appid.txt"); // moved in 2.8 + if (modsDir.Exists) { foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories()) @@ -438,14 +437,13 @@ namespace StardewModdingApi.Installer { // copy SMAPI files to game dir this.PrintDebug("Adding SMAPI files..."); - foreach (FileInfo sourceFile in paths.PackageDir.EnumerateFiles().Where(this.ShouldCopyFile)) + foreach (FileSystemInfo sourceEntry in paths.PackageDir.EnumerateFileSystemInfos().Where(this.ShouldCopy)) { - if (sourceFile.Name == this.InstallerFileName) + if (sourceEntry.Name == this.InstallerFileName) continue; - string targetPath = Path.Combine(paths.GameDir.FullName, sourceFile.Name); - this.InteractivelyDelete(targetPath); - sourceFile.CopyTo(targetPath); + this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name)); + this.RecursiveCopy(sourceEntry, paths.GameDir); } // replace mod launcher (if possible) @@ -508,7 +506,7 @@ namespace StardewModdingApi.Installer targetDir.Create(); // copy files - foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile)) + foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopy)) sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); } @@ -690,6 +688,31 @@ namespace StardewModdingApi.Installer } } + /// <summary>Recursively copy a directory or file.</summary> + /// <param name="source">The file or folder to copy.</param> + /// <param name="targetFolder">The folder to copy into.</param> + private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder) + { + if (!targetFolder.Exists) + targetFolder.Create(); + + switch (source) + { + case FileInfo sourceFile: + sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name)); + break; + + case DirectoryInfo sourceDir: + DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)); + foreach (var entry in sourceDir.EnumerateFileSystemInfos()) + this.RecursiveCopy(entry, targetSubfolder); + break; + + default: + throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'."); + } + } + /// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary> /// <param name="entry">The file or folder to reset.</param> /// <remarks>This method is mirred from <c>FileUtilities.ForceDelete</c> in the toolkit.</remarks> @@ -871,7 +894,7 @@ namespace StardewModdingApi.Installer this.PrintDebug(" Support for mods here was dropped in SMAPI 1.0 (it was never officially supported)."); // move mods if no conflicts (else warn) - foreach (FileSystemInfo entry in modDir.EnumerateFileSystemInfos().Where(this.ShouldCopyFile)) + foreach (FileSystemInfo entry in modDir.EnumerateFileSystemInfos().Where(this.ShouldCopy)) { // get type bool isDir = entry is DirectoryInfo; @@ -928,22 +951,26 @@ namespace StardewModdingApi.Installer Directory.CreateDirectory(newPath); DirectoryInfo directory = (DirectoryInfo)entry; - foreach (FileSystemInfo child in directory.EnumerateFileSystemInfos().Where(this.ShouldCopyFile)) + foreach (FileSystemInfo child in directory.EnumerateFileSystemInfos().Where(this.ShouldCopy)) this.Move(child, Path.Combine(newPath, child.Name)); directory.Delete(recursive: true); } } - /// <summary>Get whether a file should be copied when moving a folder.</summary> - /// <param name="file">The file info.</param> - private bool ShouldCopyFile(FileSystemInfo file) + /// <summary>Get whether a file or folder should be copied from the installer files.</summary> + /// <param name="entry">The file or folder info.</param> + private bool ShouldCopy(FileSystemInfo entry) { - // ignore Mac symlink - if (file is FileInfo && file.Name == "mcs") - return false; - - return true; + switch (entry.Name) + { + case "mcs": + return false; // ignore Mac symlink + case "Mods": + return false; // Mods folder handled separately + default: + return true; + } } } } diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index d1c8a4eb..db9fe8bd 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -136,7 +136,7 @@ <Private Condition="$(CopyModReferencesToBuildOutput)">true</Private> </Reference> <Reference Include="StardewModdingAPI.Toolkit.CoreInterfaces"> - <HintPath>$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath> + <HintPath>$(GamePath)\smapi-internal\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath> <Private>false</Private> <Private Condition="$(CopyModReferencesToBuildOutput)">true</Private> </Reference> diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec index 3d6f2598..04880101 100644 --- a/src/SMAPI.ModBuildConfig/package.nuspec +++ b/src/SMAPI.ModBuildConfig/package.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <metadata> <id>Pathoschild.Stardew.ModBuildConfig</id> - <version>2.1.0</version> + <version>2.1.1</version> <title>Build package for SMAPI mods</title> <authors>Pathoschild</authors> <owners>Pathoschild</owners> @@ -19,6 +19,9 @@ - Added option to ignore files by regex pattern. - Added reference to new SMAPI DLL. - Fixed some game paths not detected by NuGet package. + + 2.1.1: + - Update for SMAPI 2.8. </releaseNotes> </metadata> </package> diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index bd512fb1..0e0ae239 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -64,11 +64,14 @@ namespace StardewModdingAPI /// <summary>The URL of the SMAPI home page.</summary> internal const string HomePageUrl = "https://smapi.io"; + /// <summary>The absolute path to the folder containing SMAPI's internal files.</summary> + internal static readonly string InternalFilesPath = Program.DllSearchPath; + /// <summary>The file path for the SMAPI configuration file.</summary> - internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); + internal static string ApiConfigPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.config.json"); /// <summary>The file path for the SMAPI metadata file.</summary> - internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json"); + internal static string ApiMetadataPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.metadata.json"); /// <summary>The filename prefix used for all SMAPI logs.</summary> internal static string LogNamePrefix { get; } = "SMAPI-"; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 37b1a378..e750c659 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -45,6 +45,7 @@ namespace StardewModdingAPI.Framework.ModLoading this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform)); this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver()); this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.ExecutionPath); + this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.InternalFilesPath); // generate type => assembly lookup for types which should be rewritten this.TypeAssemblies = new Dictionary<string, Assembly>(); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index c40d2ff6..64eeb45a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -45,6 +45,11 @@ namespace StardewModdingAPI /********* ** Properties *********/ + /// <summary>The absolute path to search for SMAPI's internal DLLs.</summary> + /// <remarks>We can't use <see cref="Constants.ExecutionPath"/> directly, since <see cref="Constants"/> depends on DLLs loaded from this folder.</remarks> + [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")] + internal static readonly string DllSearchPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "smapi-internal"); + /// <summary>The log file to which to write messages.</summary> private readonly LogFileManager LogFile; @@ -111,6 +116,8 @@ namespace StardewModdingAPI /// <param name="args">The command-line arguments.</param> public static void Main(string[] args) { + // initial setup + AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; Program.AssertMinimumCompatibility(); // get flags from arguments @@ -135,10 +142,48 @@ namespace StardewModdingAPI program.RunInteractively(); } + /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> + public void Dispose() + { + // skip if already disposed + if (this.IsDisposed) + return; + this.IsDisposed = true; + this.Monitor.Log("Disposing...", LogLevel.Trace); + + // dispose mod data + foreach (IModMetadata mod in this.ModRegistry.GetAll()) + { + try + { + (mod.Mod as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + mod.LogAsMod($"Mod failed during disposal: {ex.GetLogSummary()}.", LogLevel.Warn); + } + } + + // dispose core components + this.IsGameRunning = false; + this.ConsoleManager?.Dispose(); + this.ContentCore?.Dispose(); + this.CancellationTokenSource?.Dispose(); + this.GameInstance?.Dispose(); + this.LogFile?.Dispose(); + + // end game (moved from Game1.OnExiting to let us clean up first) + Process.GetCurrentProcess().Kill(); + } + + + /********* + ** Private methods + *********/ /// <summary>Construct an instance.</summary> /// <param name="modsPath">The path to search for mods.</param> /// <param name="writeToConsole">Whether to output log messages to the console.</param> - public Program(string modsPath, bool writeToConsole) + private Program(string modsPath, bool writeToConsole) { // init paths this.VerifyPath(modsPath); @@ -189,7 +234,7 @@ namespace StardewModdingAPI /// <summary>Launch SMAPI.</summary> [HandleProcessCorruptedStateExceptions, SecurityCritical] // let try..catch handle corrupted state exceptions - public void RunInteractively() + private void RunInteractively() { // initialise SMAPI try @@ -320,44 +365,28 @@ namespace StardewModdingAPI } } - /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> - public void Dispose() + /// <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) { - // skip if already disposed - if (this.IsDisposed) - return; - this.IsDisposed = true; - this.Monitor.Log("Disposing...", LogLevel.Trace); - - // dispose mod data - foreach (IModMetadata mod in this.ModRegistry.GetAll()) + AssemblyName name = new AssemblyName(e.Name); + foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll")) { try { - (mod.Mod as IDisposable)?.Dispose(); + if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase)) + return Assembly.LoadFrom(dll.FullName); } catch (Exception ex) { - mod.LogAsMod($"Mod failed during disposal: {ex.GetLogSummary()}.", LogLevel.Warn); + throw new InvalidOperationException($"Could not load dependency 'smapi-lib/{dll.Name}'. Consider deleting the smapi-lib folder and reinstalling SMAPI.", ex); } } - // dispose core components - this.IsGameRunning = false; - this.ConsoleManager?.Dispose(); - this.ContentCore?.Dispose(); - this.CancellationTokenSource?.Dispose(); - this.GameInstance?.Dispose(); - this.LogFile?.Dispose(); - - // end game (moved from Game1.OnExiting to let us clean up first) - Process.GetCurrentProcess().Kill(); + return null; } - - /********* - ** Private methods - *********/ /// <summary>Assert that the minimum conditions are present to initialise SMAPI without type load exceptions.</summary> private static void AssertMinimumCompatibility() { |