summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md65
-rw-r--r--release-notes.md25
-rw-r--r--src/GlobalAssemblyInfo.cs4
-rw-r--r--src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs2
-rw-r--r--src/StardewModdingAPI.Installer/InteractiveInstaller.cs58
-rw-r--r--src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj44
-rw-r--r--src/StardewModdingAPI.Installer/readme.txt27
-rw-r--r--src/StardewModdingAPI.sln1
-rw-r--r--src/StardewModdingAPI/Constants.cs2
-rw-r--r--src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs4
-rw-r--r--src/StardewModdingAPI/Extensions.cs30
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs61
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyLoader.cs (renamed from src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs)142
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyParseResult.cs31
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs61
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs33
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs49
-rw-r--r--src/StardewModdingAPI/Framework/ModAssemblyLoader.cs143
-rw-r--r--src/StardewModdingAPI/Program.cs62
-rw-r--r--src/StardewModdingAPI/SemanticVersion.cs2
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj10
-rw-r--r--src/crossplatform.targets1
-rw-r--r--src/prepare-install-package.targets52
23 files changed, 416 insertions, 493 deletions
diff --git a/README.md b/README.md
index 995bb59f..b1062077 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-![](https://raw.githubusercontent.com/Gormogon/SMAPI/master/docs/imgs/SMAPI.png)
+![](docs/imgs/SMAPI.png)
**SMAPI** is an [open-source](LICENSE) modding API for [Stardew Valley](http://stardewvalley.net/).
It takes care of loading mods into the game context, and exposes events they can use to interact
@@ -70,46 +70,47 @@ folder containing `src`).
2. In Linux or Mac:
1. Rebuild the solution in _Release_ mode.
- 2. Copy `bin/Packaged/Mono` into the `SMAPI <version>` folder.
+ 2. Copy `bin/internal/Packaged/Mono` into the `SMAPI <version>` folder.
3. If you did everything right so far, you should have a folder like this:
```
SMAPI-1.x/
- Mono/
- Mods/*
- Mono.Cecil.dll
- Mono.Cecil.Rocks.dll
- Newtonsoft.Json.dll
- StardewModdingAPI
- StardewModdingAPI.config.json
- StardewModdingAPI.data.json
- StardewModdingAPI.exe
- StardewModdingAPI.exe.mdb
- StardewModdingAPI.AssemblyRewriters.dll
- System.Numerics.dll
- System.Runtime.Caching.dll
- steam_appid.txt
- Windows/
- Mods/*
- Mono.Cecil.dll
- Mono.Cecil.Rocks.dll
- Newtonsoft.Json.dll
- StardewModdingAPI.config.json
- StardewModdingAPI.data.json
- StardewModdingAPI.exe
- StardewModdingAPI.pdb
- StardewModdingAPI.xml
- StardewModdingAPI.AssemblyRewriters.dll
- steam_appid.txt
install.exe
readme.txt
+ internal/
+ Mono/
+ Mods/*
+ Mono.Cecil.dll
+ Mono.Cecil.Rocks.dll
+ Newtonsoft.Json.dll
+ StardewModdingAPI
+ StardewModdingAPI.AssemblyRewriters.dll
+ StardewModdingAPI.config.json
+ StardewModdingAPI.data.json
+ StardewModdingAPI.exe
+ StardewModdingAPI.exe.mdb
+ steam_appid.txt
+ System.Numerics.dll
+ System.Runtime.Caching.dll
+ Windows/
+ Mods/*
+ Mono.Cecil.dll
+ Mono.Cecil.Rocks.dll
+ Newtonsoft.Json.dll
+ StardewModdingAPI.AssemblyRewriters.dll
+ StardewModdingAPI.config.json
+ StardewModdingAPI.data.json
+ StardewModdingAPI.exe
+ StardewModdingAPI.pdb
+ StardewModdingAPI.xml
+ steam_appid.txt
```
- 4. Open a terminal in the `SMAPI <version>` folder and run `chmod 755 Mono/StardewModdingAPI`.
+ 4. Open a terminal in the `SMAPI <version>` folder and run `chmod 755 internal/Mono/StardewModdingAPI`.
5. Copy & paste the `SMAPI <version>` folder as `SMAPI <version> for developers`.
6. In the `SMAPI <version>` folder, delete the following files:
- * `Mono/StardewModdingAPI.config.json`
- * `Windows/StardewModdingAPI.config.json`
- * `Windows/StardewModdingAPI.xml`
+ * `internal/Mono/StardewModdingAPI.config.json`
+ * `internal/Windows/StardewModdingAPI.config.json`
+ * `internal/Windows/StardewModdingAPI.xml`
7. Compress the two folders into `SMAPI <version>.zip` and `SMAPI <version> for developers.zip`.
## Advanced usage
diff --git a/release-notes.md b/release-notes.md
index 4e7b3a30..1ff868a4 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -1,5 +1,30 @@
# Release notes
+## 1.8
+See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8).
+
+For players:
+* Mods no longer generate `.cache` subfolders.
+* Fixed multiple issues where mods failed during assembly loading.
+* Tweaked install package to reduce confusion.
+
+For mod developers:
+* The `SemanticVersion` constructor now accepts a string version.
+* Increased deprecation level for `Extensions` to _pending removal_.
+* **Warning:** `Assembly.GetExecutingAssembly().Location` will no longer reliably
+ return a valid path, because mod assemblies are loaded from memory when rewritten for
+ compatibility. This approach has been discouraged since SMAPI 1.3; use `helper.DirectoryPath`
+ instead.
+
+For SMAPI developers:
+* Rewrote assembly loading from the ground up. The new implementation...
+ * is much simpler;
+ * eliminates the `.cache` folders by loading rewritten assemblies from memory;
+ * ensures DLLs are loaded in leaf-to-root order (i.e. dependencies first);
+ * improves dependent assembly resolution;
+ * no longer loads DLLs if they're not referenced;
+ * reduces log verbosity.
+
## 1.7
See [log](https://github.com/Pathoschild/SMAPI/compare/1.6...1.7).
diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs
index 29e5dae7..b591153a 100644
--- a/src/GlobalAssemblyInfo.cs
+++ b/src/GlobalAssemblyInfo.cs
@@ -2,5 +2,5 @@
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
-[assembly: AssemblyVersion("1.7.0.0")]
-[assembly: AssemblyFileVersion("1.7.0.0")] \ No newline at end of file
+[assembly: AssemblyVersion("1.8.0.0")]
+[assembly: AssemblyFileVersion("1.8.0.0")] \ No newline at end of file
diff --git a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs
index f2826080..fce2b187 100644
--- a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs
+++ b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs
@@ -20,8 +20,6 @@ namespace StardewModdingAPI.AssemblyRewriters
/// <summary>The short assembly names to remove as assembly reference, and replace with the <see cref="Targets"/>. These should be short names (like "Stardew Valley").</summary>
public readonly string[] RemoveNames;
- /// <summary>The assembly filenames to target. Equivalent types should be rewritten to use these assemblies.</summary>
-
/****
** Metadata
****/
diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
index ef813eb3..5abcfc8f 100644
--- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
+++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs
@@ -27,6 +27,7 @@ namespace StardewModdingApi.Installer
yield return $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/Steam/steamapps/common/Stardew Valley";
// Mac
+ yield return "/Applications/Stardew Valley.app/Contents/MacOS";
yield return $"{Environment.GetEnvironmentVariable("HOME")}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS";
// Windows
@@ -50,32 +51,38 @@ namespace StardewModdingApi.Installer
}
}
- /// <summary>The directory or file paths to remove when uninstalling SMAPI, relative to the game directory.</summary>
- private readonly string[] UninstallPaths =
+ /// <summary>Get the absolute file or folder paths to remove when uninstalling SMAPI.</summary>
+ /// <param name="installDir">The folder for Stardew Valley and SMAPI.</param>
+ /// <param name="modsDir">The folder for SMAPI mods.</param>
+ private IEnumerable<string> GetUninstallPaths(DirectoryInfo installDir, DirectoryInfo modsDir)
{
+ Func<string, string> installPath = path => Path.Combine(installDir.FullName, path);
+
// common
- "StardewModdingAPI.exe",
- "StardewModdingAPI.config.json",
- "StardewModdingAPI.data.json",
- "StardewModdingAPI.AssemblyRewriters.dll",
- "steam_appid.txt",
+ yield return installPath("StardewModdingAPI.exe");
+ yield return installPath("StardewModdingAPI.config.json");
+ yield return installPath("StardewModdingAPI.data.json");
+ yield return installPath("StardewModdingAPI.AssemblyRewriters.dll");
+ yield return installPath("steam_appid.txt");
// Linux/Mac only
- "Mono.Cecil.dll",
- "Mono.Cecil.Rocks.dll",
- "Newtonsoft.Json.dll",
- "StardewModdingAPI",
- "StardewModdingAPI.exe.mdb",
- "System.Numerics.dll",
- "System.Runtime.Caching.dll",
+ yield return installPath("Mono.Cecil.dll");
+ yield return installPath("Mono.Cecil.Rocks.dll");
+ yield return installPath("Newtonsoft.Json.dll");
+ yield return installPath("StardewModdingAPI");
+ yield return installPath("StardewModdingAPI.exe.mdb");
+ yield return installPath("System.Numerics.dll");
+ yield return installPath("System.Runtime.Caching.dll");
// Windows only
- "StardewModdingAPI.pdb",
+ yield return installPath("StardewModdingAPI.pdb");
// obsolete
- "Mods/.cache", // 1.3-1.4
- "StardewModdingAPI-settings.json" // 1.0-1.4
- };
+ yield return installPath("Mods/.cache"); // 1.3-1.4
+ yield return installPath("StardewModdingAPI-settings.json"); // 1.0-1.4
+ foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories())
+ yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7
+ }
/// <summary>Whether the current console supports color formatting.</summary>
private static readonly bool ConsoleSupportsColor = InteractiveInstaller.GetConsoleSupportsColor();
@@ -109,8 +116,9 @@ namespace StardewModdingApi.Installer
** collect details
****/
Platform platform = this.DetectPlatform();
- DirectoryInfo packageDir = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), platform.ToString()));
+ DirectoryInfo packageDir = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "internal", platform.ToString()));
DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform);
+ DirectoryInfo modsDir = new DirectoryInfo(Path.Combine(installDir.FullName, "Mods"));
var paths = new
{
executable = Path.Combine(installDir.FullName, platform == Platform.Mono ? "StardewValley.exe" : "Stardew Valley.exe"),
@@ -125,7 +133,7 @@ namespace StardewModdingApi.Installer
****/
if (!packageDir.Exists)
{
- this.ExitError($"The '{platform}' package directory is missing (should be at {packageDir}).");
+ this.ExitError($"The 'internal/{platform}' package folder is missing (should be at {packageDir}).");
return;
}
if (!File.Exists(paths.executable))
@@ -139,11 +147,9 @@ namespace StardewModdingApi.Installer
** ask user what to do
****/
Console.WriteLine("You can....");
- Console.WriteLine(platform == Platform.Mono
- ? "[1] Install SMAPI. This will safely update the files so you can launch the game the same way as before."
- : "[1] Install SMAPI. You'll need to launch StardewModdingAPI.exe instead afterwards; see the readme.txt for details."
- );
+ Console.WriteLine("[1] Install SMAPI.");
Console.WriteLine("[2] Uninstall SMAPI.");
+ Console.WriteLine();
ScriptAction action;
{
@@ -175,8 +181,7 @@ namespace StardewModdingApi.Installer
}
// remove old files
- string[] removePaths = this.UninstallPaths
- .Select(path => Path.Combine(installDir.FullName, path))
+ string[] removePaths = this.GetUninstallPaths(installDir, modsDir)
.Where(path => Directory.Exists(path) || File.Exists(path))
.ToArray();
if (removePaths.Any())
@@ -219,7 +224,6 @@ namespace StardewModdingApi.Installer
}
// create mods directory (if needed)
- DirectoryInfo modsDir = new DirectoryInfo(Path.Combine(installDir.FullName, "Mods"));
if (!modsDir.Exists)
{
this.PrintDebug("Creating mods directory...");
diff --git a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
index 4e4872b6..e31a1452 100644
--- a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
+++ b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
@@ -50,47 +50,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
- <Import Project="$(SolutionDir)\crossplatform.targets" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- package files -->
- <Target Name="AfterBuild">
- <PropertyGroup>
- <CompiledInstallerPath>$(SolutionDir)\..\bin\Packaged</CompiledInstallerPath>
- <CompiledSmapiPath>$(SolutionDir)\..\bin\$(Configuration)\SMAPI</CompiledSmapiPath>
- </PropertyGroup>
- <ItemGroup>
- <CompiledMods Include="$(SolutionDir)\..\bin\$(Configuration)\Mods\**\*.*" />
- </ItemGroup>
- <!-- reset package directory -->
- <RemoveDir Directories="$(CompiledInstallerPath)" />
- <!-- copy installer files -->
- <Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(CompiledInstallerPath)\install.exe" />
- <Copy SourceFiles="$(TargetDir)\readme.txt" DestinationFiles="$(CompiledInstallerPath)\readme.txt" />
- <!-- copy SMAPI files for Mono -->
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.Rocks.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe.mdb" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.AssemblyRewriters.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.data.json" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\unix-launcher.sh" DestinationFiles="$(CompiledInstallerPath)\Mono\StardewModdingAPI" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(CompiledInstallerPath)\Mono" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(CompiledInstallerPath)\Mono\Mods\%(RecursiveDir)" />
- <!-- copy SMAPI files for Windows -->
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.Rocks.dll" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.AssemblyRewriters.dll" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.data.json" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(CompiledInstallerPath)\Windows" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(CompiledInstallerPath)\Windows\Mods\%(RecursiveDir)" />
- </Target>
+ <Import Project="$(SolutionDir)\crossplatform.targets" />
+ <Import Project="$(SolutionDir)\prepare-install-package.targets" />
</Project> \ No newline at end of file
diff --git a/src/StardewModdingAPI.Installer/readme.txt b/src/StardewModdingAPI.Installer/readme.txt
index c5a1caa6..4756099e 100644
--- a/src/StardewModdingAPI.Installer/readme.txt
+++ b/src/StardewModdingAPI.Installer/readme.txt
@@ -1,4 +1,25 @@
-On Windows, double-click install.exe.
-On Linux or Mac, open a terminal and run `mono install.exe`.
+ ___ ___ ___ ___
+ / /\ /__/\ / /\ / /\ ___
+ / /:/_ | |::\ / /::\ / /::\ / /\
+ / /:/ /\ | |:|:\ / /:/\:\ / /:/\:\ / /:/
+ / /:/ /::\ __|__|:|\:\ / /:/~/::\ / /:/~/:/ /__/::\
+ /__/:/ /:/\:\ /__/::::| \:\ /__/:/ /:/\:\ /__/:/ /:/ \__\/\:\__
+ \ \:\/:/~/:/ \ \:\~~\__\/ \ \:\/:/__\/ \ \:\/:/ \ \:\/\
+ \ \::/ /:/ \ \:\ \ \::/ \ \::/ \__\::/
+ \__\/ /:/ \ \:\ \ \:\ \ \:\ /__/:/
+ /__/:/ \ \:\ \ \:\ \ \:\ \__\/
+ \__\/ \__\/ \__\/ \__\/
-For more detailed instructions, see http://canimod.com/guides/using-mods#installing-smapi. \ No newline at end of file
+
+SMAPI lets you run Stardew Valley with mods. Don't forget to download mods separately.
+
+
+To install:
+ - Windows: double-click install.exe.
+ - Linux or Mac: open a terminal and run `mono install.exe`.
+
+
+Need help? See:
+ - Install guide: http://canimod.com/guides/using-mods#installing-smapi
+ - Troubleshooting: http://canimod.com/guides/smapi-faq#troubleshooting
+ - Ask for help: https://discord.gg/kH55QXP
diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln
index 7229cf30..8ab297ed 100644
--- a/src/StardewModdingAPI.sln
+++ b/src/StardewModdingAPI.sln
@@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "metadata", "metadata", "{86
crossplatform.targets = crossplatform.targets
GlobalAssemblyInfo.cs = GlobalAssemblyInfo.cs
..\LICENSE = ..\LICENSE
+ prepare-install-package.targets = prepare-install-package.targets
..\README.md = ..\README.md
..\release-notes.md = ..\release-notes.md
EndProjectSection
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs
index df527dfe..a62a0d58 100644
--- a/src/StardewModdingAPI/Constants.cs
+++ b/src/StardewModdingAPI/Constants.cs
@@ -30,7 +30,7 @@ namespace StardewModdingAPI
public static readonly Version Version = (Version)Constants.ApiVersion;
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion => new Version(1, 7, 0, null, suppressDeprecationWarning: true);
+ public static ISemanticVersion ApiVersion => new Version(1, 8, 0, null, suppressDeprecationWarning: true);
/// <summary>The minimum supported version of Stardew Valley.</summary>
public const string MinimumGameVersion = "1.1";
diff --git a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
index 443429aa..aa0bb377 100644
--- a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
+++ b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs
@@ -9,10 +9,10 @@ namespace StardewModdingAPI.Events
/*********
** Accessors
*********/
- /// <summary>The player's previous location.</summary>
+ /// <summary>The player's current location.</summary>
public GameLocation NewLocation { get; private set; }
- /// <summary>The player's current location.</summary>
+ /// <summary>The player's previous location.</summary>
public GameLocation PriorLocation { get; private set; }
diff --git a/src/StardewModdingAPI/Extensions.cs b/src/StardewModdingAPI/Extensions.cs
index 1229ca97..0e9dbbf7 100644
--- a/src/StardewModdingAPI/Extensions.cs
+++ b/src/StardewModdingAPI/Extensions.cs
@@ -27,7 +27,7 @@ namespace StardewModdingAPI
{
get
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Random)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Random)}", "1.0", DeprecationLevel.PendingRemoval);
return Extensions._random;
}
}
@@ -40,7 +40,7 @@ namespace StardewModdingAPI
/// <param name="key">The key to check.</param>
public static bool IsKeyDown(this Keys key)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsKeyDown)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsKeyDown)}", "1.0", DeprecationLevel.PendingRemoval);
return Keyboard.GetState().IsKeyDown(key);
}
@@ -48,7 +48,7 @@ namespace StardewModdingAPI
/// <summary>Get a random color.</summary>
public static Color RandomColour()
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RandomColour)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RandomColour)}", "1.0", DeprecationLevel.PendingRemoval);
return new Color(Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255));
}
@@ -69,7 +69,7 @@ namespace StardewModdingAPI
/// <param name="split">The value separator.</param>
public static string ToSingular<T>(this IEnumerable<T> ienum, string split = ", ")
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "1.0", DeprecationLevel.PendingRemoval);
//Apparently Keys[] won't split normally :l
if (typeof(T) == typeof(Keys))
@@ -83,7 +83,7 @@ namespace StardewModdingAPI
/// <param name="o">The value.</param>
public static bool IsInt32(this object o)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsInt32)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsInt32)}", "1.0", DeprecationLevel.PendingRemoval);
int i;
return int.TryParse(o.ToString(), out i);
@@ -93,7 +93,7 @@ namespace StardewModdingAPI
/// <param name="o">The value.</param>
public static int AsInt32(this object o)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsInt32)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsInt32)}", "1.0", DeprecationLevel.PendingRemoval);
return int.Parse(o.ToString());
}
@@ -102,7 +102,7 @@ namespace StardewModdingAPI
/// <param name="o">The value.</param>
public static bool IsBool(this object o)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsBool)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsBool)}", "1.0", DeprecationLevel.PendingRemoval);
bool b;
return bool.TryParse(o.ToString(), out b);
@@ -112,7 +112,7 @@ namespace StardewModdingAPI
/// <param name="o">The value.</param>
public static bool AsBool(this object o)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsBool)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsBool)}", "1.0", DeprecationLevel.PendingRemoval);
return bool.Parse(o.ToString());
}
@@ -121,7 +121,7 @@ namespace StardewModdingAPI
/// <param name="enumerable">The values to hash.</param>
public static int GetHash(this IEnumerable enumerable)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetHash)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetHash)}", "1.0", DeprecationLevel.PendingRemoval);
var hash = 0;
foreach (var v in enumerable)
@@ -134,7 +134,7 @@ namespace StardewModdingAPI
/// <param name="o">The value.</param>
public static T Cast<T>(this object o) where T : class
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Cast)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Cast)}", "1.0", DeprecationLevel.PendingRemoval);
return o as T;
}
@@ -143,7 +143,7 @@ namespace StardewModdingAPI
/// <param name="o">The object to scan.</param>
public static FieldInfo[] GetPrivateFields(this object o)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetPrivateFields)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetPrivateFields)}", "1.0", DeprecationLevel.PendingRemoval);
return o.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
}
@@ -152,7 +152,7 @@ namespace StardewModdingAPI
/// <param name="name">The name of the field to find.</param>
public static FieldInfo GetBaseFieldInfo(this Type t, string name)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval);
return t.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
}
@@ -162,7 +162,7 @@ namespace StardewModdingAPI
/// <param name="name">The name of the field to find.</param>
public static T GetBaseFieldValue<T>(this Type t, object o, string name) where T : class
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval);
return t.GetBaseFieldInfo(name).GetValue(o) as T;
}
@@ -173,7 +173,7 @@ namespace StardewModdingAPI
/// <param name="newValue">The value to set.</param>
public static void SetBaseFieldValue<T>(this Type t, object o, string name, object newValue) where T : class
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.SetBaseFieldValue)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.SetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval);
t.GetBaseFieldInfo(name).SetValue(o, newValue as T);
}
@@ -181,7 +181,7 @@ namespace StardewModdingAPI
/// <param name="st">The string to copy.</param>
public static string RemoveNumerics(this string st)
{
- Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RemoveNumerics)}", "1.0", DeprecationLevel.Info);
+ Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RemoveNumerics)}", "1.0", DeprecationLevel.PendingRemoval);
var s = st;
foreach (var c in s)
{
diff --git a/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs b/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs
new file mode 100644
index 00000000..b4e69fcd
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using Mono.Cecil;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary>
+ internal class AssemblyDefinitionResolver : DefaultAssemblyResolver
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The known assemblies.</summary>
+ private readonly IDictionary<string, AssemblyDefinition> Loaded = new Dictionary<string, AssemblyDefinition>();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Add known assemblies to the resolver.</summary>
+ /// <param name="assemblies">The known assemblies.</param>
+ public void Add(params AssemblyDefinition[] assemblies)
+ {
+ foreach (AssemblyDefinition assembly in assemblies)
+ {
+ this.Loaded[assembly.Name.Name] = assembly;
+ this.Loaded[assembly.Name.FullName] = assembly;
+ }
+ }
+
+ /// <summary>Resolve an assembly reference.</summary>
+ /// <param name="name">The assembly name.</param>
+ public override AssemblyDefinition Resolve(AssemblyNameReference name) => this.ResolveName(name.Name) ?? base.Resolve(name);
+
+ /// <summary>Resolve an assembly reference.</summary>
+ /// <param name="name">The assembly name.</param>
+ /// <param name="parameters">The assembly reader parameters.</param>
+ public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) => this.ResolveName(name.Name) ?? base.Resolve(name, parameters);
+
+ /// <summary>Resolve an assembly reference.</summary>
+ /// <param name="fullName">The assembly full name (including version, etc).</param>
+ public override AssemblyDefinition Resolve(string fullName) => this.ResolveName(fullName) ?? base.Resolve(fullName);
+
+ /// <summary>Resolve an assembly reference.</summary>
+ /// <param name="fullName">The assembly full name (including version, etc).</param>
+ /// <param name="parameters">The assembly reader parameters.</param>
+ public override AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) => this.ResolveName(fullName) ?? base.Resolve(fullName, parameters);
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Resolve a known assembly definition based on its short or full name.</summary>
+ /// <param name="name">The assembly's short or full name.</param>
+ private AssemblyDefinition ResolveName(string name)
+ {
+ return this.Loaded.ContainsKey(name)
+ ? this.Loaded[name]
+ : null;
+ }
+ }
+}
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs
index 9d4d6b11..123211b9 100644
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
+++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
@@ -6,15 +8,15 @@ using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using StardewModdingAPI.AssemblyRewriters;
-namespace StardewModdingAPI.Framework.AssemblyRewriting
+namespace StardewModdingAPI.Framework
{
- /// <summary>Rewrites type references.</summary>
- internal class AssemblyTypeRewriter
+ /// <summary>Preprocesses and loads mod assemblies.</summary>
+ internal class AssemblyLoader
{
/*********
** Properties
*********/
- /// <summary>Metadata for mapping assemblies to the current <see cref="Platform"/>.</summary>
+ /// <summary>Metadata for mapping assemblies to the current platform.</summary>
private readonly PlatformAssemblyMap AssemblyMap;
/// <summary>A type => assembly lookup for types which should be rewritten.</summary>
@@ -28,17 +30,16 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current <see cref="Platform"/>.</param>
+ /// <param name="targetPlatform">The current game platform.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public AssemblyTypeRewriter(PlatformAssemblyMap assemblyMap, IMonitor monitor)
+ public AssemblyLoader(Platform targetPlatform, IMonitor monitor)
{
- // save config
- this.AssemblyMap = assemblyMap;
this.Monitor = monitor;
+ this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform);
- // collect type => assembly lookup
+ // generate type => assembly lookup for types which should be rewritten
this.TypeAssemblies = new Dictionary<string, Assembly>();
- foreach (Assembly assembly in assemblyMap.Targets)
+ foreach (Assembly assembly in this.AssemblyMap.Targets)
{
ModuleDefinition module = this.AssemblyMap.TargetModules[assembly];
foreach (TypeDefinition type in module.GetTypes())
@@ -52,10 +53,107 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
}
}
+ /// <summary>Preprocess and load an assembly.</summary>
+ /// <param name="assemblyPath">The assembly file path.</param>
+ /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
+ public Assembly Load(string assemblyPath)
+ {
+ // get referenced local assemblies
+ AssemblyParseResult[] assemblies;
+ {
+ AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver();
+ assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), new HashSet<string>(), resolver).ToArray();
+ if (!assemblies.Any())
+ throw new InvalidOperationException($"Could not load '{assemblyPath}' because it doesn't exist.");
+ resolver.Add(assemblies.Select(p => p.Definition).ToArray());
+ }
+
+ // rewrite & load assemblies in leaf-to-root order
+ Assembly lastAssembly = null;
+ foreach (AssemblyParseResult assembly in assemblies)
+ {
+ this.Monitor.Log($"Loading {assembly.File.Name}...", LogLevel.Trace);
+ bool changed = this.RewriteAssembly(assembly.Definition);
+ if (changed)
+ {
+ using (MemoryStream outStream = new MemoryStream())
+ {
+ assembly.Definition.Write(outStream);
+ byte[] bytes = outStream.ToArray();
+ lastAssembly = Assembly.Load(bytes);
+ }
+ }
+ else
+ lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName);
+ }
+
+ // last assembly loaded is the root
+ return lastAssembly;
+ }
+
+ /// <summary>Resolve an assembly by its name.</summary>
+ /// <param name="name">The assembly name.</param>
+ /// <remarks>
+ /// This implementation returns the first loaded assembly which matches the short form of
+ /// the assembly name, to resolve assembly resolution issues when rewriting
+ /// assemblies (especially with Mono). Since this is meant to be called on <see cref="AppDomain.AssemblyResolve"/>,
+ /// the implicit assumption is that loading the exact assembly failed.
+ /// </remarks>
+ public Assembly ResolveAssembly(string name)
+ {
+ string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture)
+ return AppDomain.CurrentDomain
+ .GetAssemblies()
+ .FirstOrDefault(p => p.GetName().Name == shortName);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /****
+ ** Assembly parsing
+ ****/
+ /// <summary>Get a list of referenced local assemblies starting from the mod assembly, ordered from leaf to root.</summary>
+ /// <param name="file">The assembly file to load.</param>
+ /// <param name="visitedAssemblyPaths">The assembly paths that should be skipped.</param>
+ /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
+ private IEnumerable<AssemblyParseResult> GetReferencedLocalAssemblies(FileInfo file, HashSet<string> visitedAssemblyPaths, IAssemblyResolver assemblyResolver)
+ {
+ // validate
+ if (file.Directory == null)
+ throw new InvalidOperationException($"Could not get directory from file path '{file.FullName}'.");
+ if (visitedAssemblyPaths.Contains(file.FullName))
+ yield break; // already visited
+ if (!file.Exists)
+ yield break; // not a local assembly
+ visitedAssemblyPaths.Add(file.FullName);
+
+ // read assembly
+ byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
+ AssemblyDefinition assembly;
+ using (Stream readStream = new MemoryStream(assemblyBytes))
+ assembly = AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver });
+
+ // yield referenced assemblies
+ foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences)
+ {
+ FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll"));
+ foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyPaths, assemblyResolver))
+ yield return result;
+ }
+
+ // yield assembly
+ yield return new AssemblyParseResult(file, assembly);
+ }
+
+ /****
+ ** Assembly rewriting
+ ****/
/// <summary>Rewrite the types referenced by an assembly.</summary>
/// <param name="assembly">The assembly to rewrite.</param>
/// <returns>Returns whether the assembly was modified.</returns>
- public bool RewriteAssembly(AssemblyDefinition assembly)
+ private bool RewriteAssembly(AssemblyDefinition assembly)
{
ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module
@@ -65,7 +163,6 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
{
if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
{
- this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace);
shouldRewrite = true;
module.AssemblyReferences.RemoveAt(i);
i--;
@@ -76,19 +173,12 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
// add target assembly references
foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
- {
- this.Monitor.Log($" adding reference to {target}", LogLevel.Trace);
module.AssemblyReferences.Add(target);
- }
// rewrite type scopes to use target assemblies
IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
- string lastTypeLogged = null;
foreach (TypeReference type in typeReferences)
- {
- this.ChangeTypeScope(type, shouldLog: type.FullName != lastTypeLogged);
- lastTypeLogged = type.FullName;
- }
+ this.ChangeTypeScope(type);
// rewrite incompatible methods
IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray();
@@ -111,7 +201,6 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
if (rewriter != null)
{
MethodReference methodRef = (MethodReference)op.Operand;
- this.Monitor.Log($"rewriting method reference {methodRef.DeclaringType.FullName}.{methodRef.Name}", LogLevel.Trace);
rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap);
}
}
@@ -121,14 +210,9 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
return true;
}
-
- /*********
- ** Private methods
- *********/
/// <summary>Get the correct reference to use for compatibility with the current platform.</summary>
/// <param name="type">The type reference to rewrite.</param>
- /// <param name="shouldLog">Whether to log a message.</param>
- private void ChangeTypeScope(TypeReference type, bool shouldLog)
+ private void ChangeTypeScope(TypeReference type)
{
// check skip conditions
if (type == null || type.FullName.StartsWith("System."))
@@ -141,8 +225,6 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
// replace scope
AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly];
- if (shouldLog)
- this.Monitor.Log($"redirecting {type.FullName} from {type.Scope.Name} to {assemblyRef.Name}", LogLevel.Trace);
type.Scope = assemblyRef;
}
diff --git a/src/StardewModdingAPI/Framework/AssemblyParseResult.cs b/src/StardewModdingAPI/Framework/AssemblyParseResult.cs
new file mode 100644
index 00000000..bff976aa
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/AssemblyParseResult.cs
@@ -0,0 +1,31 @@
+using System.IO;
+using Mono.Cecil;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>Metadata about a parsed assembly definition.</summary>
+ internal class AssemblyParseResult
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The original assembly file.</summary>
+ public readonly FileInfo File;
+
+ /// <summary>The assembly definition.</summary>
+ public readonly AssemblyDefinition Definition;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="file">The original assembly file.</param>
+ /// <param name="assembly">The assembly definition.</param>
+ public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly)
+ {
+ this.File = file;
+ this.Definition = assembly;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
deleted file mode 100644
index 4c3b86fe..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.IO;
-using StardewModdingAPI.AssemblyRewriters;
-
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Represents cached metadata for a rewritten assembly.</summary>
- internal class CacheEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The MD5 hash for the original assembly.</summary>
- public readonly string Hash;
-
- /// <summary>The SMAPI version used to rewrite the assembly.</summary>
- public readonly string ApiVersion;
-
- /// <summary>The target platform.</summary>
- public readonly Platform Platform;
-
- /// <summary>The <see cref="System.Environment.MachineName"/> value for the machine used to rewrite the assembly.</summary>
- public readonly string MachineName;
-
- /// <summary>Whether to use the cached assembly instead of the original assembly.</summary>
- public readonly bool UseCachedAssembly;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="hash">The MD5 hash for the original assembly.</param>
- /// <param name="apiVersion">The SMAPI version used to rewrite the assembly.</param>
- /// <param name="platform">The target platform.</param>
- /// <param name="machineName">The <see cref="System.Environment.MachineName"/> value for the machine used to rewrite the assembly.</param>
- /// <param name="useCachedAssembly">Whether to use the cached assembly instead of the original assembly.</param>
- public CacheEntry(string hash, string apiVersion, Platform platform, string machineName, bool useCachedAssembly)
- {
- this.Hash = hash;
- this.ApiVersion = apiVersion;
- this.Platform = platform;
- this.MachineName = machineName;
- this.UseCachedAssembly = useCachedAssembly;
- }
-
- /// <summary>Get whether the cache entry is up-to-date for the given assembly hash.</summary>
- /// <param name="paths">The paths for the cached assembly.</param>
- /// <param name="hash">The MD5 hash of the original assembly.</param>
- /// <param name="currentVersion">The current SMAPI version.</param>
- /// <param name="platform">The target platform.</param>
- /// <param name="machineName">The <see cref="System.Environment.MachineName"/> value for the machine reading the assembly.</param>
- public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion, Platform platform, string machineName)
- {
- return hash == this.Hash
- && this.ApiVersion == currentVersion.ToString()
- && this.Platform == platform
- && this.MachineName == machineName
- && (!this.UseCachedAssembly || File.Exists(paths.Assembly));
- }
- }
-} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs
deleted file mode 100644
index 18861873..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Contains the paths for an assembly's cached data.</summary>
- internal struct CachePaths
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The directory path which contains the assembly.</summary>
- public string Directory { get; }
-
- /// <summary>The file path of the assembly file.</summary>
- public string Assembly { get; }
-
- /// <summary>The file path containing the assembly metadata.</summary>
- public string Metadata { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="directory">The directory path which contains the assembly.</param>
- /// <param name="assembly">The file path of the assembly file.</param>
- /// <param name="metadata">The file path containing the assembly metadata.</param>
- public CachePaths(string directory, string assembly, string metadata)
- {
- this.Directory = directory;
- this.Assembly = assembly;
- this.Metadata = metadata;
- }
- }
-} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs
deleted file mode 100644
index 8f34bb20..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Metadata about a preprocessed assembly.</summary>
- internal class RewriteResult
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The original assembly path.</summary>
- public readonly string OriginalAssemblyPath;
-
- /// <summary>The cache paths.</summary>
- public readonly CachePaths CachePaths;
-
- /// <summary>The rewritten assembly bytes.</summary>
- public readonly byte[] AssemblyBytes;
-
- /// <summary>The MD5 hash for the original assembly.</summary>
- public readonly string Hash;
-
- /// <summary>Whether to use the cached assembly instead of the original assembly.</summary>
- public readonly bool UseCachedAssembly;
-
- /// <summary>Whether this data is newer than the cache.</summary>
- public readonly bool IsNewerThanCache;
-
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="originalAssemblyPath"></param>
- /// <param name="cachePaths">The cache paths.</param>
- /// <param name="assemblyBytes">The rewritten assembly bytes.</param>
- /// <param name="hash">The MD5 hash for the original assembly.</param>
- /// <param name="useCachedAssembly">Whether to use the cached assembly instead of the original assembly.</param>
- /// <param name="isNewerThanCache">Whether this data is newer than the cache.</param>
- public RewriteResult(string originalAssemblyPath, CachePaths cachePaths, byte[] assemblyBytes, string hash, bool useCachedAssembly, bool isNewerThanCache)
- {
- this.OriginalAssemblyPath = originalAssemblyPath;
- this.CachePaths = cachePaths;
- this.Hash = hash;
- this.AssemblyBytes = assemblyBytes;
- this.UseCachedAssembly = useCachedAssembly;
- this.IsNewerThanCache = isNewerThanCache;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
deleted file mode 100644
index e4760398..00000000
--- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Security.Cryptography;
-using Mono.Cecil;
-using Newtonsoft.Json;
-using StardewModdingAPI.AssemblyRewriters;
-using StardewModdingAPI.Framework.AssemblyRewriting;
-
-namespace StardewModdingAPI.Framework
-{
- /// <summary>Preprocesses and loads mod assemblies.</summary>
- internal class ModAssemblyLoader
- {
- /*********
- ** Properties
- *********/
- /// <summary>The name of the directory containing a mod's cached data.</summary>
- private readonly string CacheDirName;
-
- /// <summary>Metadata for mapping assemblies to the current <see cref="Platform"/>.</summary>
- private readonly PlatformAssemblyMap AssemblyMap;
-
- /// <summary>Rewrites assembly types to match the current platform.</summary>
- private readonly AssemblyTypeRewriter AssemblyTypeRewriter;
-
- /// <summary>Encapsulates monitoring and logging.</summary>
- private readonly IMonitor Monitor;
-
- /// <summary>The current game platform.</summary>
- private readonly Platform TargetPlatform;
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="cacheDirName">The name of the directory containing a mod's cached data.</param>
- /// <param name="targetPlatform">The current game platform.</param>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- public ModAssemblyLoader(string cacheDirName, Platform targetPlatform, IMonitor monitor)
- {
- this.CacheDirName = cacheDirName;
- this.TargetPlatform = targetPlatform;
- this.Monitor = monitor;
- this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform);
- this.AssemblyTypeRewriter = new AssemblyTypeRewriter(this.AssemblyMap, monitor);
- }
-
- /// <summary>Preprocess an assembly unless the cache is up to date.</summary>
- /// <param name="assemblyPath">The assembly file path.</param>
- /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
- public RewriteResult ProcessAssemblyUnlessCached(string assemblyPath)
- {
- // read assembly data
- byte[] assemblyBytes = File.ReadAllBytes(assemblyPath);
- string hash = string.Join("", MD5.Create().ComputeHash(assemblyBytes).Select(p => p.ToString("X2")));
-
- // get cached result if current
- CachePaths cachePaths = this.GetCachePaths(assemblyPath);
- {
- CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject<CacheEntry>(File.ReadAllText(cachePaths.Metadata)) : null;
- if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion, this.TargetPlatform, Environment.MachineName))
- return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed
- }
- this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace);
-
- // rewrite assembly
- AssemblyDefinition assembly;
- using (Stream readStream = new MemoryStream(assemblyBytes))
- assembly = AssemblyDefinition.ReadAssembly(readStream);
- bool modified = this.AssemblyTypeRewriter.RewriteAssembly(assembly);
- using (MemoryStream outStream = new MemoryStream())
- {
- assembly.Write(outStream);
- byte[] outBytes = outStream.ToArray();
- return new RewriteResult(assemblyPath, cachePaths, outBytes, hash, useCachedAssembly: modified, isNewerThanCache: true);
- }
- }
-
- /// <summary>Write rewritten assembly metadata to the cache for a mod.</summary>
- /// <param name="results">The rewrite results.</param>
- /// <param name="forceCacheAssemblies">Whether to write all assemblies to the cache, even if they weren't modified.</param>
- /// <exception cref="InvalidOperationException">There are no results to write, or the results are not all for the same directory.</exception>
- public void WriteCache(IEnumerable<RewriteResult> results, bool forceCacheAssemblies)
- {
- results = results.ToArray();
-
- // get cache directory
- if (!results.Any())
- throw new InvalidOperationException("There are no assemblies to cache.");
- if (results.Select(p => p.CachePaths.Directory).Distinct().Count() > 1)
- throw new InvalidOperationException("The assemblies can't be cached together because they have different source directories.");
- string cacheDir = results.Select(p => p.CachePaths.Directory).First();
-
- // reset cache
- if (Directory.Exists(cacheDir))
- Directory.Delete(cacheDir, recursive: true);
- Directory.CreateDirectory(cacheDir);
-
- // cache all results
- foreach (RewriteResult result in results)
- {
- CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), this.TargetPlatform, Environment.MachineName, forceCacheAssemblies || result.UseCachedAssembly);
- File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry));
- if (forceCacheAssemblies || result.UseCachedAssembly)
- File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes);
- }
- }
-
- /// <summary>Resolve an assembly from its name.</summary>
- /// <param name="name">The assembly name.</param>
- /// <remarks>
- /// This implementation returns the first loaded assembly which matches the short form of
- /// the assembly name, to resolve assembly resolution issues when rewriting
- /// assemblies (especially with Mono). Since this is meant to be called on <see cref="AppDomain.AssemblyResolve"/>,
- /// the implicit assumption is that loading the exact assembly failed.
- /// </remarks>
- public Assembly ResolveAssembly(string name)
- {
- string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture)
- return AppDomain.CurrentDomain
- .GetAssemblies()
- .FirstOrDefault(p => p.GetName().Name == shortName);
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Get the cache details for an assembly.</summary>
- /// <param name="assemblyPath">The assembly file path.</param>
- private CachePaths GetCachePaths(string assemblyPath)
- {
- string fileName = Path.GetFileName(assemblyPath);
- string dirPath = Path.Combine(Path.GetDirectoryName(assemblyPath), this.CacheDirName);
- string cacheAssemblyPath = Path.Combine(dirPath, fileName);
- string metadataPath = Path.Combine(dirPath, $"{fileName}.json");
- return new CachePaths(dirPath, cacheAssemblyPath, metadataPath);
- }
- }
-}
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index ec3ccce7..45bf1238 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -13,7 +13,6 @@ using Newtonsoft.Json;
using StardewModdingAPI.AssemblyRewriters;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
-using StardewModdingAPI.Framework.AssemblyRewriting;
using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Inheritance;
using StardewValley;
@@ -41,9 +40,6 @@ namespace StardewModdingAPI
/// <summary>The full path to the folder containing mods.</summary>
private static readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods");
- /// <summary>The name of the folder containing a mod's cached assembly data.</summary>
- private static readonly string CacheDirName = ".cache";
-
/// <summary>The log file to which to write messages.</summary>
private static readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath);
@@ -319,7 +315,7 @@ namespace StardewModdingAPI
Program.Monitor.Log("Loading mods...");
// get assembly loader
- ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CacheDirName, Program.TargetPlatform, Program.Monitor);
+ AssemblyLoader modAssemblyLoader = new AssemblyLoader(Program.TargetPlatform, Program.Monitor);
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name);
// get known incompatible mods
@@ -342,10 +338,6 @@ namespace StardewModdingAPI
{
string directoryName = new DirectoryInfo(directory).Name;
- // ignore internal directory
- if (directoryName == ".cache")
- continue;
-
// check for cancellation
if (Program.CancellationTokenSource.IsCancellationRequested)
{
@@ -458,47 +450,29 @@ namespace StardewModdingAPI
}
}
- // preprocess mod assemblies for compatibility
- var processedAssemblies = new List<RewriteResult>();
+ // validate mod path to simplify errors
+ string assemblyPath = Path.Combine(directory, manifest.EntryDll);
+ if (!File.Exists(assemblyPath))
{
- bool succeeded = true;
- foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
- {
- try
- {
- processedAssemblies.Add(modAssemblyLoader.ProcessAssemblyUnlessCached(assemblyPath));
- }
- catch (Exception ex)
- {
- Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{Path.GetFileName(assemblyPath)}'.\n{ex.GetLogSummary()}", LogLevel.Error);
- succeeded = false;
- break;
- }
- }
- if (!succeeded)
- continue;
+ Program.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error);
+ continue;
}
- bool forceUseCachedAssembly = processedAssemblies.Any(p => p.UseCachedAssembly); // make sure DLLs are kept together for dependency resolution
- if (processedAssemblies.Any(p => p.IsNewerThanCache))
- modAssemblyLoader.WriteCache(processedAssemblies, forceUseCachedAssembly);
- // get entry assembly path
- string mainAssemblyPath;
+ // preprocess & load mod assembly
+ Assembly modAssembly;
+ try
{
- RewriteResult mainProcessedAssembly = processedAssemblies.FirstOrDefault(p => p.OriginalAssemblyPath == Path.Combine(directory, manifest.EntryDll));
- if (mainProcessedAssembly == null)
- {
- Program.Monitor.Log($"{errorPrefix}: the specified mod DLL does not exist.", LogLevel.Error);
- continue;
- }
- mainAssemblyPath = forceUseCachedAssembly ? mainProcessedAssembly.CachePaths.Assembly : mainProcessedAssembly.OriginalAssemblyPath;
+ modAssembly = modAssemblyLoader.Load(assemblyPath);
+ }
+ catch (Exception ex)
+ {
+ Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{manifest.EntryDll}'.\n{ex.GetLogSummary()}", LogLevel.Error);
+ continue;
}
- // load entry assembly
- Assembly modAssembly;
+ // validate assembly
try
{
- modAssembly = Assembly.UnsafeLoadFrom(mainAssemblyPath); // unsafe load allows downloaded DLLs
if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0)
{
Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error);
@@ -507,11 +481,11 @@ namespace StardewModdingAPI
}
catch (Exception ex)
{
- Program.Monitor.Log($"{errorPrefix}: an error occurred while optimising the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error);
+ Program.Monitor.Log($"{errorPrefix}: an error occurred while reading the mod DLL.\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
- // get mod instance
+ // initialise mod
Mod mod;
try
{
diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs
index daefda51..c29f2cf7 100644
--- a/src/StardewModdingAPI/SemanticVersion.cs
+++ b/src/StardewModdingAPI/SemanticVersion.cs
@@ -56,7 +56,7 @@ namespace StardewModdingAPI
/// <summary>Construct an instance.</summary>
/// <param name="version">The semantic version string.</param>
/// <exception cref="FormatException">The <paramref name="version"/> is not a valid semantic version.</exception>
- internal SemanticVersion(string version)
+ public SemanticVersion(string version)
{
var match = SemanticVersion.Regex.Match(version);
if (!match.Success)
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index d56b6866..337929e2 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -148,6 +148,8 @@
<Compile Include="Events\EventArgsStringChanged.cs" />
<Compile Include="Events\GameEvents.cs" />
<Compile Include="Events\GraphicsEvents.cs" />
+ <Compile Include="Framework\AssemblyDefinitionResolver.cs" />
+ <Compile Include="Framework\AssemblyParseResult.cs" />
<Compile Include="IModRegistry.cs" />
<Compile Include="Events\LocationEvents.cs" />
<Compile Include="Events\MenuEvents.cs" />
@@ -156,15 +158,11 @@
<Compile Include="Events\SaveEvents.cs" />
<Compile Include="Events\TimeEvents.cs" />
<Compile Include="Extensions.cs" />
- <Compile Include="Framework\AssemblyRewriting\RewriteResult.cs" />
- <Compile Include="Framework\AssemblyRewriting\CachePaths.cs" />
- <Compile Include="Framework\AssemblyRewriting\AssemblyTypeRewriter.cs" />
- <Compile Include="Framework\AssemblyRewriting\CacheEntry.cs" />
<Compile Include="Framework\DeprecationLevel.cs" />
<Compile Include="Framework\DeprecationManager.cs" />
<Compile Include="Framework\InternalExtensions.cs" />
<Compile Include="Framework\Models\IncompatibleMod.cs" />
- <Compile Include="Framework\ModAssemblyLoader.cs" />
+ <Compile Include="Framework\AssemblyLoader.cs" />
<Compile Include="Framework\Reflection\CacheEntry.cs" />
<Compile Include="Framework\Reflection\PrivateField.cs" />
<Compile Include="Framework\Reflection\PrivateMethod.cs" />
@@ -268,4 +266,4 @@
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Rocks.dll" DestinationFolder="$(GamePath)" />
</Target>
-</Project>
+</Project> \ No newline at end of file
diff --git a/src/crossplatform.targets b/src/crossplatform.targets
index ef2d341f..f28e005e 100644
--- a/src/crossplatform.targets
+++ b/src/crossplatform.targets
@@ -4,6 +4,7 @@
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
<!-- Mac paths -->
+ <GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
<!-- Windows paths -->
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
diff --git a/src/prepare-install-package.targets b/src/prepare-install-package.targets
new file mode 100644
index 00000000..f411b909
--- /dev/null
+++ b/src/prepare-install-package.targets
@@ -0,0 +1,52 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--
+
+ This build task is run from the installer project after all projects have been compiled, and
+ creates the build package in the bin\Packages folder.
+
+ -->
+ <Target Name="AfterBuild">
+ <PropertyGroup>
+ <CompiledSmapiPath>$(SolutionDir)\..\bin\$(Configuration)\SMAPI</CompiledSmapiPath>
+ <PackagePath>$(SolutionDir)\..\bin\Packaged</PackagePath>
+ <PackageInternalPath>$(PackagePath)\internal</PackageInternalPath>
+ </PropertyGroup>
+ <ItemGroup>
+ <CompiledMods Include="$(SolutionDir)\..\bin\$(Configuration)\Mods\**\*.*" />
+ </ItemGroup>
+ <!-- reset package directory -->
+ <RemoveDir Directories="$(PackagePath)" />
+
+ <!-- copy installer files -->
+ <Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackagePath)\install.exe" />
+ <Copy SourceFiles="$(TargetDir)\readme.txt" DestinationFiles="$(PackagePath)\README.txt" />
+
+ <!-- copy SMAPI files for Mono -->
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.Rocks.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe.mdb" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.AssemblyRewriters.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.data.json" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\unix-launcher.sh" DestinationFiles="$(PackageInternalPath)\Mono\StardewModdingAPI" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackageInternalPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(PackageInternalPath)\Mono\Mods\%(RecursiveDir)" />
+
+ <!-- copy SMAPI files for Windows -->
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.Rocks.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.AssemblyRewriters.dll" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.data.json" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackageInternalPath)\Windows" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="@(CompiledMods)" DestinationFolder="$(PackageInternalPath)\Windows\Mods\%(RecursiveDir)" />
+ </Target>
+</Project>