summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs115
-rw-r--r--src/SMAPI.ModBuildConfig/build/smapi.targets2
-rw-r--r--src/SMAPI.ModBuildConfig/package.nuspec5
-rw-r--r--src/SMAPI/Constants.cs7
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs1
-rw-r--r--src/SMAPI/Program.cs85
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()
{