summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Installer/Framework/InstallerContext.cs45
-rw-r--r--src/SMAPI.Installer/Framework/InstallerPaths.cs44
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs122
-rw-r--r--src/SMAPI.Installer/Program.cs3
-rw-r--r--src/SMAPI.Installer/SMAPI.Installer.csproj2
-rw-r--r--src/SMAPI.Installer/assets/README.txt2
-rw-r--r--src/SMAPI.Installer/assets/System.Numerics.dllbin54272 -> 0 bytes
-rw-r--r--src/SMAPI.Installer/assets/System.Runtime.Caching.dllbin71168 -> 0 bytes
-rw-r--r--src/SMAPI.Installer/assets/runtimeconfig.unix.json14
-rw-r--r--src/SMAPI.Installer/assets/runtimeconfig.windows.json12
-rw-r--r--src/SMAPI.Installer/assets/unix-install.sh24
-rw-r--r--src/SMAPI.Installer/assets/unix-launcher.sh113
-rw-r--r--src/SMAPI.Installer/assets/windows-install.bat53
-rw-r--r--src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj11
-rw-r--r--src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj3
-rw-r--r--src/SMAPI.ModBuildConfig/DeployModTask.cs64
-rw-r--r--src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs21
-rw-r--r--src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs126
-rw-r--r--src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj16
-rw-r--r--src/SMAPI.ModBuildConfig/build/smapi.targets59
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs221
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj28
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs9
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj27
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/SMAPI.Tests.csproj8
-rw-r--r--src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs8
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj2
-rw-r--r--src/SMAPI.Toolkit/Framework/Constants.cs9
-rw-r--r--src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs5
-rw-r--r--src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs9
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj6
-rw-r--r--src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs7
-rw-r--r--src/SMAPI.Toolkit/Utilities/FileUtilities.cs13
-rw-r--r--src/SMAPI.Toolkit/Utilities/PathUtilities.cs45
-rw-r--r--src/SMAPI.Web/Controllers/IndexController.cs52
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs7
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs3
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs7
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs15
-rw-r--r--src/SMAPI.Web/SMAPI.Web.csproj18
-rw-r--r--src/SMAPI.Web/ViewModels/IndexModel.cs15
-rw-r--r--src/SMAPI.Web/Views/Index/Index.cshtml57
-rw-r--r--src/SMAPI.Web/appsettings.json10
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/index.css4
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json65
-rw-r--r--src/SMAPI.Web/wwwroot/schemas/content-patcher.json6
-rw-r--r--src/SMAPI/Constants.cs95
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForMap.cs90
-rw-r--r--src/SMAPI/Framework/Content/ContentCache.cs21
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs41
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs17
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs14
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs19
-rw-r--r--src/SMAPI/Framework/InternalExtensions.cs7
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs33
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs5
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs10
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs33
-rw-r--r--src/SMAPI/Framework/SGame.cs162
-rw-r--r--src/SMAPI/Framework/SModHooks.cs30
-rw-r--r--src/SMAPI/GameFramework.cs7
-rw-r--r--src/SMAPI/IAssetDataForMap.cs3
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs13
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs10
-rw-r--r--src/SMAPI/PatchMapMode.cs15
-rw-r--r--src/SMAPI/Program.cs33
-rw-r--r--src/SMAPI/SMAPI.config.json2
-rw-r--r--src/SMAPI/SMAPI.csproj40
74 files changed, 1145 insertions, 965 deletions
diff --git a/src/SMAPI.Installer/Framework/InstallerContext.cs b/src/SMAPI.Installer/Framework/InstallerContext.cs
index 88e57760..95df32ca 100644
--- a/src/SMAPI.Installer/Framework/InstallerContext.cs
+++ b/src/SMAPI.Installer/Framework/InstallerContext.cs
@@ -1,6 +1,4 @@
-using System;
using System.IO;
-using Microsoft.Win32;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.GameScanning;
using StardewModdingAPI.Toolkit.Utilities;
@@ -13,9 +11,6 @@ namespace StardewModdingAPI.Installer.Framework
/*********
** Fields
*********/
- /// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary>
- private readonly Version Windows7Version = new Version(6, 1);
-
/// <summary>The underlying toolkit game scanner.</summary>
private readonly GameScanner GameScanner = new GameScanner();
@@ -29,9 +24,6 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>The human-readable OS name and version.</summary>
public string PlatformName { get; }
- /// <summary>The name of the Stardew Valley executable.</summary>
- public string ExecutableName { get; }
-
/// <summary>Whether the installer is running on Windows.</summary>
public bool IsWindows => this.Platform == Platform.Windows;
@@ -47,7 +39,6 @@ namespace StardewModdingAPI.Installer.Framework
{
this.Platform = EnvironmentUtility.DetectPlatform();
this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform);
- this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform);
}
/// <summary>Get the installer's version number.</summary>
@@ -57,42 +48,6 @@ namespace StardewModdingAPI.Installer.Framework
return new SemanticVersion(raw);
}
- /// <summary>Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows.</summary>
- /// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
- public bool HasNetFramework45()
- {
- switch (this.Platform)
- {
- case Platform.Windows:
- using (RegistryKey versionKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"))
- return versionKey?.GetValue("Release") != null; // .NET Framework 4.5+
-
- default:
- throw new NotSupportedException("The installed .NET Framework version can only be checked on Windows.");
- }
- }
-
- /// <summary>Get whether the current system has XNA Framework installed. This only applies on Windows.</summary>
- /// <exception cref="NotSupportedException">The current platform is not Windows.</exception>
- public bool HasXna()
- {
- switch (this.Platform)
- {
- case Platform.Windows:
- using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\XNA\Framework"))
- return key != null; // XNA Framework 4.0+
-
- default:
- throw new NotSupportedException("The installed XNA Framework version can only be checked on Windows.");
- }
- }
-
- /// <summary>Whether the current OS supports newer versions of .NET Framework.</summary>
- public bool CanInstallLatestNetFramework()
- {
- return Environment.OSVersion.Version >= this.Windows7Version; // Windows 7+
- }
-
/// <summary>Get whether a folder seems to contain the game files.</summary>
/// <param name="dir">The folder to check.</param>
public bool LooksLikeGameFolder(DirectoryInfo dir)
diff --git a/src/SMAPI.Installer/Framework/InstallerPaths.cs b/src/SMAPI.Installer/Framework/InstallerPaths.cs
index 6ba5fa5f..0976eceb 100644
--- a/src/SMAPI.Installer/Framework/InstallerPaths.cs
+++ b/src/SMAPI.Installer/Framework/InstallerPaths.cs
@@ -1,4 +1,5 @@
using System.IO;
+using StardewModdingAPI.Toolkit.Framework;
namespace StardewModdingAPI.Installer.Framework
{
@@ -44,17 +45,20 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>The full path to the user's config overrides file.</summary>
public string ApiUserConfigPath { get; }
- /// <summary>The full path to the installed game executable file.</summary>
- public string ExecutablePath { get; private set; }
+ /// <summary>The full path to the installed game DLL.</summary>
+ public string GameDllPath { get; }
- /// <summary>The full path to the vanilla game launcher on Linux/macOS.</summary>
- public string UnixLauncherPath { get; }
+ /// <summary>The full path to the installed SMAPI executable file.</summary>
+ public string UnixSmapiExecutablePath { get; }
- /// <summary>The full path to the installed SMAPI launcher on Linux/macOS before it's renamed.</summary>
- public string UnixSmapiLauncherPath { get; }
+ /// <summary>The full path to the vanilla game launch script on Linux/macOS.</summary>
+ public string VanillaLaunchScriptPath { get; }
- /// <summary>The full path to the vanilla game launcher on Linux/macOS after SMAPI is installed.</summary>
- public string UnixBackupLauncherPath { get; }
+ /// <summary>The full path to the installed SMAPI launch script on Linux/macOS before it's renamed.</summary>
+ public string NewLaunchScriptPath { get; }
+
+ /// <summary>The full path to the backed up game launch script on Linux/macOS after SMAPI is installed.</summary>
+ public string BackupLaunchScriptPath { get; }
/*********
@@ -63,28 +67,24 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>Construct an instance.</summary>
/// <param name="bundleDir">The directory path containing the files to copy into the game folder.</param>
/// <param name="gameDir">The directory path for the installed game.</param>
- /// <param name="gameExecutableName">The name of the game's executable file for the current platform.</param>
- public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir, string gameExecutableName)
+ public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir)
{
+ // base paths
this.BundleDir = bundleDir;
this.GameDir = gameDir;
this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods"));
+ this.GameDllPath = Path.Combine(gameDir.FullName, Constants.GameDllName);
- this.BundleApiUserConfigPath = Path.Combine(bundleDir.FullName, "smapi-internal", "config.user.json");
+ // launch scripts
+ this.VanillaLaunchScriptPath = Path.Combine(gameDir.FullName, "StardewValley");
+ this.NewLaunchScriptPath = Path.Combine(gameDir.FullName, "unix-launcher.sh");
+ this.BackupLaunchScriptPath = Path.Combine(gameDir.FullName, "StardewValley-original");
+ this.UnixSmapiExecutablePath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
- this.ExecutablePath = Path.Combine(gameDir.FullName, gameExecutableName);
- this.UnixLauncherPath = Path.Combine(gameDir.FullName, "StardewValley");
- this.UnixSmapiLauncherPath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
- this.UnixBackupLauncherPath = Path.Combine(gameDir.FullName, "StardewValley-original");
+ // internal files
+ this.BundleApiUserConfigPath = Path.Combine(bundleDir.FullName, "smapi-internal", "config.user.json");
this.ApiConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "config.json");
this.ApiUserConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "config.user.json");
}
-
- /// <summary>Override the filename for the <see cref="ExecutablePath"/>.</summary>
- /// <param name="filename">the file name.</param>
- public void SetExecutableFileName(string filename)
- {
- this.ExecutablePath = Path.Combine(this.GamePath, filename);
- }
}
}
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 9f49137f..d8c27a2d 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -39,18 +39,19 @@ namespace StardewModdingApi.Installer
string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
// current files
- yield return GetInstallPath("libgdiplus.dylib"); // Linux/macOS only
yield return GetInstallPath("StardewModdingAPI"); // Linux/macOS only
+ yield return GetInstallPath("StardewModdingAPI.dll");
yield return GetInstallPath("StardewModdingAPI.exe");
yield return GetInstallPath("StardewModdingAPI.exe.config");
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/macOS only
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
+ yield return GetInstallPath("StardewModdingAPI.runtimeconfig.json");
yield return GetInstallPath("StardewModdingAPI.xml");
- yield return GetInstallPath("StardewModdingAPI-x64.exe"); // not normally added to game folder, but may be mistakenly added by a manual install
yield return GetInstallPath("smapi-internal");
yield return GetInstallPath("steam_appid.txt");
// obsolete
+ yield return GetInstallPath("libgdiplus.dylib"); // before 3.13 (macOS only)
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
@@ -70,9 +71,7 @@ namespace StardewModdingApi.Installer
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("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("StardewModdingAPI-x64.exe"); // before 3.13
if (modsDir.Exists)
{
@@ -150,30 +149,6 @@ namespace StardewModdingApi.Installer
#endif
/****
- ** Check Windows dependencies
- ****/
- if (context.IsWindows)
- {
- // .NET Framework 4.5+
- if (!context.HasNetFramework45())
- {
- this.PrintError(context.CanInstallLatestNetFramework()
- ? "Please install the latest version of .NET Framework before installing SMAPI."
- : "Please install .NET Framework 4.5 before installing SMAPI."
- );
- this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details.");
- Console.ReadLine();
- return;
- }
- if (!context.HasXna())
- {
- this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup.");
- Console.ReadLine();
- return;
- }
- }
-
- /****
** read command-line arguments
****/
// get action from CLI
@@ -270,51 +245,20 @@ namespace StardewModdingApi.Installer
// get folders
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
- paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName);
+ paths = new InstallerPaths(bundleDir, installDir);
}
/*********
** Step 4: validate assumptions
*********/
- // not 64-bit on Windows
- if (context.Platform == Platform.Windows)
- {
- FileInfo linuxExecutable = new FileInfo(Path.Combine(paths.GamePath, "StardewValley.exe"));
- if (linuxExecutable.Exists && this.Is64Bit(linuxExecutable.FullName))
- {
- this.PrintError("Oops! The detected game install path seems to be unofficial 64-bit mode, which is no longer supported. You can update to Stardew Valley 1.5.5 or later instead. See https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows for more info.");
- Console.ReadLine();
- return;
- }
- }
-
// executable exists
- if (!File.Exists(paths.ExecutablePath))
+ if (!File.Exists(paths.GameDllPath))
{
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
Console.ReadLine();
return;
}
-
- // not Stardew Valley 1.5.5+
- if (File.Exists(Path.Combine(paths.GamePath, "Stardew Valley.dll")))
- {
- this.PrintError("Oops! The detected game install path seems to be Stardew Valley 1.5.5 or later, but this version of SMAPI is only compatible up to Stardew Valley 1.5.4. Please check for a newer version of SMAPI: https://smapi.io.");
- Console.ReadLine();
- return;
- }
-
- // game folder doesn't contain paths beyond the max limit
- {
- string[] tooLongPaths = PathUtilities.GetTooLongPaths(Path.Combine(paths.GamePath, "Mods")).ToArray();
- if (tooLongPaths.Any())
- {
- this.PrintError($"SMAPI can't install to the detected game folder, because some of its files exceed the maximum {context.Platform} path length.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", tooLongPaths)}");
- Console.ReadLine();
- return;
- }
- }
Console.Clear();
@@ -387,11 +331,11 @@ namespace StardewModdingApi.Installer
** Always uninstall old files
****/
// restore game launcher
- if (context.IsUnix && File.Exists(paths.UnixBackupLauncherPath))
+ if (context.IsUnix && File.Exists(paths.BackupLaunchScriptPath))
{
this.PrintDebug("Removing SMAPI launcher...");
- this.InteractivelyDelete(paths.UnixLauncherPath);
- File.Move(paths.UnixBackupLauncherPath, paths.UnixLauncherPath);
+ this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
+ File.Move(paths.BackupLaunchScriptPath, paths.VanillaLaunchScriptPath);
}
// remove old files
@@ -439,30 +383,41 @@ namespace StardewModdingApi.Installer
this.PrintDebug("Safely replacing game launcher...");
// back up & remove current launcher
- if (File.Exists(paths.UnixLauncherPath))
+ if (File.Exists(paths.VanillaLaunchScriptPath))
{
- if (!File.Exists(paths.UnixBackupLauncherPath))
- File.Move(paths.UnixLauncherPath, paths.UnixBackupLauncherPath);
+ if (!File.Exists(paths.BackupLaunchScriptPath))
+ File.Move(paths.VanillaLaunchScriptPath, paths.BackupLaunchScriptPath);
else
- this.InteractivelyDelete(paths.UnixLauncherPath);
+ this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
}
// add new launcher
- File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath);
+ File.Move(paths.NewLaunchScriptPath, paths.VanillaLaunchScriptPath);
- // mark file executable
+ // mark files executable
// (MSBuild doesn't keep permission flags for files zipped in a build task.)
- new Process
+ foreach (string path in new[] { paths.VanillaLaunchScriptPath, paths.UnixSmapiExecutablePath })
{
- StartInfo = new ProcessStartInfo
+ new Process
{
- FileName = "chmod",
- Arguments = $"755 \"{paths.UnixLauncherPath}\"",
- CreateNoWindow = true
- }
- }.Start();
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "chmod",
+ Arguments = $"755 \"{path}\"",
+ CreateNoWindow = true
+ }
+ }.Start();
+ }
}
+ // copy the game's deps.json file
+ // (This is needed to resolve native DLLs like libSkiaSharp.)
+ File.Copy(
+ sourceFileName: Path.Combine(paths.GamePath, "Stardew Valley.deps.json"),
+ destFileName: Path.Combine(paths.GamePath, "StardewModdingAPI.deps.json"),
+ overwrite: true
+ );
+
// create mods directory (if needed)
if (!paths.ModsDir.Exists)
{
@@ -527,7 +482,7 @@ namespace StardewModdingApi.Installer
/*********
- ** Step 6: final instructions
+ ** Step 7: final instructions
*********/
if (context.IsWindows)
{
@@ -556,13 +511,6 @@ namespace StardewModdingApi.Installer
/*********
** Private methods
*********/
- /// <summary>Get whether an executable is 64-bit.</summary>
- /// <param name="executablePath">The absolute path to the executable file.</param>
- private bool Is64Bit(string executablePath)
- {
- return LowLevelEnvironmentUtility.Is64BitAssembly(executablePath);
- }
-
/// <summary>Get the display text for a color scheme.</summary>
/// <param name="scheme">The color scheme.</param>
private string GetDisplayText(MonitorColorScheme scheme)
@@ -725,7 +673,7 @@ namespace StardewModdingApi.Installer
{
// get path from user
Console.WriteLine();
- this.PrintInfo($"Type the file path to the game directory (the one containing '{context.ExecutableName}'), then press enter.");
+ this.PrintInfo($"Type the file path to the game directory (the one containing '{Constants.GameDllName}'), then press enter.");
string path = Console.ReadLine()?.Trim();
if (string.IsNullOrWhiteSpace(path))
{
diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs
index d9c31dd6..45cfea75 100644
--- a/src/SMAPI.Installer/Program.cs
+++ b/src/SMAPI.Installer/Program.cs
@@ -31,8 +31,7 @@ namespace StardewModdingApi.Installer
public static void Main(string[] args)
{
// find install bundle
- PlatformID platform = Environment.OSVersion.Platform;
- FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "unix")}-install.dat"));
+ FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, "install.dat"));
if (!zipFile.Exists)
{
Console.WriteLine($"Oops! Some of the installer files are missing; try re-downloading the installer. (Missing file: {zipFile.FullName})");
diff --git a/src/SMAPI.Installer/SMAPI.Installer.csproj b/src/SMAPI.Installer/SMAPI.Installer.csproj
index c47f3e6e..e3e01467 100644
--- a/src/SMAPI.Installer/SMAPI.Installer.csproj
+++ b/src/SMAPI.Installer/SMAPI.Installer.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
<Description>The SMAPI installer for players.</Description>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Installer/assets/README.txt b/src/SMAPI.Installer/assets/README.txt
index c3a7e271..5c20529a 100644
--- a/src/SMAPI.Installer/assets/README.txt
+++ b/src/SMAPI.Installer/assets/README.txt
@@ -24,7 +24,7 @@ Manual install
THIS IS NOT RECOMMENDED FOR MOST PLAYERS. See instructions above instead.
If you really want to install SMAPI manually, here's how.
-1. Unzip "internal/windows-install.dat" (on Windows) or "internal/unix-install.dat" (on
+1. Unzip "internal/windows/install.dat" (on Windows) or "internal/unix/install.dat" (on
Linux/macOS). You can change '.dat' to '.zip', it's just a normal zip file renamed to prevent
confusion.
2. Copy the files from the folder you just unzipped into your game folder. The
diff --git a/src/SMAPI.Installer/assets/System.Numerics.dll b/src/SMAPI.Installer/assets/System.Numerics.dll
deleted file mode 100644
index fed0f92c..00000000
--- a/src/SMAPI.Installer/assets/System.Numerics.dll
+++ /dev/null
Binary files differ
diff --git a/src/SMAPI.Installer/assets/System.Runtime.Caching.dll b/src/SMAPI.Installer/assets/System.Runtime.Caching.dll
deleted file mode 100644
index a062391d..00000000
--- a/src/SMAPI.Installer/assets/System.Runtime.Caching.dll
+++ /dev/null
Binary files differ
diff --git a/src/SMAPI.Installer/assets/runtimeconfig.unix.json b/src/SMAPI.Installer/assets/runtimeconfig.unix.json
new file mode 100644
index 00000000..8a01ceb1
--- /dev/null
+++ b/src/SMAPI.Installer/assets/runtimeconfig.unix.json
@@ -0,0 +1,14 @@
+{
+ "runtimeOptions": {
+ "tfm": "net5.0",
+ "includedFrameworks": [
+ {
+ "name": "Microsoft.NETCore.App",
+ "version": "5.0.7"
+ }
+ ],
+ "configProperties": {
+ "System.Runtime.TieredCompilation": false
+ }
+ }
+}
diff --git a/src/SMAPI.Installer/assets/runtimeconfig.windows.json b/src/SMAPI.Installer/assets/runtimeconfig.windows.json
new file mode 100644
index 00000000..b693d809
--- /dev/null
+++ b/src/SMAPI.Installer/assets/runtimeconfig.windows.json
@@ -0,0 +1,12 @@
+{
+ "runtimeOptions": {
+ "tfm": "net5.0",
+ "framework": {
+ "name": "Microsoft.NETCore.App",
+ "version": "5.0.0"
+ },
+ "configProperties": {
+ "System.Runtime.TieredCompilation": false
+ }
+ }
+}
diff --git a/src/SMAPI.Installer/assets/unix-install.sh b/src/SMAPI.Installer/assets/unix-install.sh
index 311c5469..07df4e6c 100644
--- a/src/SMAPI.Installer/assets/unix-install.sh
+++ b/src/SMAPI.Installer/assets/unix-install.sh
@@ -1,24 +1,14 @@
#!/bin/bash
-# Run the SMAPI installer through Mono on Linux or macOS.
# Move to script's directory
cd "`dirname "$0"`"
-# get cross-distro version of POSIX command
-COMMAND=""
-if command -v command >/dev/null 2>&1; then
- COMMAND="command -v"
-elif type type >/dev/null 2>&1; then
- COMMAND="type"
+# make sure .NET 5 is installed
+if ! command -v dotnet >/dev/null 2>&1; then
+ echo "Oops! You must have .NET 5 installed to use SMAPI: https://dotnet.microsoft.com/download";
+ read
+ exit 1
fi
-# if $TERM is not set to xterm, mono will bail out when attempting to write to the console.
-export TERM=xterm
-
-# validate Mono & run installer
-if $COMMAND mono >/dev/null 2>&1; then
- mono internal/unix-install.exe
-else
- echo "Oops! Looks like Mono isn't installed. Please install Mono from https://mono-project.com, reboot, and run this installer again."
- read
-fi
+# run installer
+dotnet internal/unix/SMAPI.Installer.dll
diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh
index d309f750..58f7a5ae 100644
--- a/src/SMAPI.Installer/assets/unix-launcher.sh
+++ b/src/SMAPI.Installer/assets/unix-launcher.sh
@@ -1,51 +1,19 @@
#!/usr/bin/env bash
-# MonoKickstart Shell Script
-# Written by Ethan "flibitijibibo" Lee
-# Modified for SMAPI by various contributors
-# Move to script's directory
+##########
+## Initial setup
+##########
+# move to script's directory
cd "$(dirname "$0")" || exit $?
-# Get the system architecture
-UNAME=$(uname)
-ARCH=$(uname -m)
-
-# MonoKickstart picks the right libfolder, so just execute the right binary.
-if [ "$UNAME" == "Darwin" ]; then
- # ... Except on OSX.
- export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./osx/
-
- # El Capitan is a total idiot and wipes this variable out, making the
- # Steam overlay disappear. This sidesteps "System Integrity Protection"
- # and resets the variable with Valve's own variable (they provided this
- # fix by the way, thanks Valve!). Note that you will need to update your
- # launch configuration to the script location, NOT just the app location
- # (i.e. Kick.app/Contents/MacOS/Kick, not just Kick.app).
- # -flibit
- if [ "$STEAM_DYLD_INSERT_LIBRARIES" != "" ] && [ "$DYLD_INSERT_LIBRARIES" == "" ]; then
- export DYLD_INSERT_LIBRARIES="$STEAM_DYLD_INSERT_LIBRARIES"
- fi
-
- # this was here before
- ln -sf mcs.bin.osx mcs
-
- # fix "DllNotFoundException: libgdiplus.dylib" errors when loading images in SMAPI
- if [ -f libgdiplus.dylib ]; then
- rm libgdiplus.dylib
- fi
- if [ -f /Library/Frameworks/Mono.framework/Versions/Current/lib/libgdiplus.dylib ]; then
- ln -s /Library/Frameworks/Mono.framework/Versions/Current/lib/libgdiplus.dylib libgdiplus.dylib
- fi
- # create bin file
- # Note: don't overwrite if it's identical, to avoid resetting permission flags
- if [ ! -x StardewModdingAPI.bin.osx ] || ! cmp StardewValley.bin.osx StardewModdingAPI.bin.osx >/dev/null 2>&1; then
- cp -p StardewValley.bin.osx StardewModdingAPI.bin.osx
- fi
-
- # Make sure we're running in Terminal (so the user can see errors/warnings/update alerts).
- # Previously we would just use `open -a Terminal` to launch the .bin.osx file, but that
- # doesn't let us set environment variables.
+##########
+## Open terminal if needed
+##########
+# on macOS, make sure we're running in a Terminal
+# Besides letting the player see errors/warnings/alerts in the console, this is also needed because
+# Steam messes with the PATH.
+if [ "$(uname)" == "Darwin" ]; then
if [ ! -t 1 ]; then # https://stackoverflow.com/q/911168/262123
# sanity check to make sure we don't have an infinite loop of opening windows
SKIP_TERMINAL=false
@@ -68,21 +36,38 @@ if [ "$UNAME" == "Darwin" ]; then
exit 0
fi
fi
+fi
+
- # launch SMAPI
- LC_ALL="C" ./StardewModdingAPI.bin.osx "$@"
+##########
+## Validate assumptions
+##########
+# script must be run from the game folder
+if [ ! -f "Stardew Valley.dll" ]; then
+ echo "Oops! SMAPI must be placed in the Stardew Valley game folder.\nSee instructions: https://stardewvalleywiki.com/Modding:Player_Guide";
+ read
+ exit 1
+fi
+
+# .NET 5 must be installed
+if ! command -v dotnet >/dev/null 2>&1; then
+ echo "Oops! You must have .NET 5 installed to use SMAPI: https://dotnet.microsoft.com/download";
+ read
+ exit 1
+fi
+
+
+##########
+## Launch SMAPI
+##########
+# macOS
+if [ "$(uname)" == "Darwin" ]; then
+ dotnet StardewModdingAPI.dll "$@"
+
+# Linux
else
# choose binary file to launch
- LAUNCH_FILE=""
- if [ "$ARCH" == "x86_64" ]; then
- ln -sf mcs.bin.x86_64 mcs
- cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64
- LAUNCH_FILE="./StardewModdingAPI.bin.x86_64"
- else
- ln -sf mcs.bin.x86 mcs
- cp StardewValley.bin.x86 StardewModdingAPI.bin.x86
- LAUNCH_FILE="./StardewModdingAPI.bin.x86"
- fi
+ LAUNCH_FILE="./StardewModdingAPI"
export LAUNCH_FILE
# select terminal (prefer xterm for best compatibility, then known supported terminals)
@@ -105,44 +90,44 @@ else
terminal|termite)
# consumes only one argument after -e
# options containing space characters are unsupported
- exec $TERMINAL_NAME -e "env TERM=xterm LC_ALL=\"C\" $LAUNCH_FILE $@"
+ exec $TERMINAL_NAME -e "env TERM=xterm $LAUNCH_FILE $@"
;;
xterm|konsole|alacritty)
# consumes all arguments after -e
- exec $TERMINAL_NAME -e env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ exec $TERMINAL_NAME -e env TERM=xterm $LAUNCH_FILE "$@"
;;
terminator|xfce4-terminal|mate-terminal)
# consumes all arguments after -x
- exec $TERMINAL_NAME -x env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ exec $TERMINAL_NAME -x env TERM=xterm $LAUNCH_FILE "$@"
;;
gnome-terminal)
# consumes all arguments after --
- exec $TERMINAL_NAME -- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@"
;;
kitty)
# consumes all trailing arguments
- exec $TERMINAL_NAME env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@"
;;
*)
# If we don't know the terminal, just try to run it in the current shell.
# If THAT fails, launch with no output.
- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ env TERM=xterm $LAUNCH_FILE "$@"
if [ $? -eq 127 ]; then
- exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@"
+ exec $LAUNCH_FILE --no-terminal "$@"
fi
esac
## terminal isn't executable; fallback to current shell or no terminal
else
echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell."
- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@"
+ env TERM=xterm $LAUNCH_FILE "$@"
if [ $? -eq 127 ]; then
- exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@"
+ exec $LAUNCH_FILE --no-terminal "$@"
fi
fi
fi
diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat
index 2cd98554..2e0be906 100644
--- a/src/SMAPI.Installer/assets/windows-install.bat
+++ b/src/SMAPI.Installer/assets/windows-install.bat
@@ -1,8 +1,49 @@
@echo off
-echo "%~dp0" | findstr /C:"%TEMP%" 1>nul
-if not errorlevel 1 (
- echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
- pause
-) else (
- start /WAIT /B internal\windows-install.exe
+
+SET installerDir=%~dp0
+
+REM make sure we're not running within a zip folder
+echo %installerDir% | findstr /C:"%TEMP%" 1>nul
+if %ERRORLEVEL% EQU 0 (
+ echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
+ echo.
+ pause
+ exit
+)
+
+REM make sure .NET 5 is installed
+WHERE dotnet /q
+if %ERRORLEVEL% NEQ 0 (
+ echo Oops! You must have .NET 5 ^(desktop x64^) installed to use SMAPI: https://dotnet.microsoft.com/download/dotnet/5.0/runtime
+ echo.
+ pause
+ exit
+)
+dotnet --info | findstr /C:"Microsoft.WindowsDesktop.App 5." 1>nul
+if %ERRORLEVEL% NEQ 0 (
+ echo Oops! You must have .NET 5 ^(desktop x64^) installed to use SMAPI: https://dotnet.microsoft.com/download/dotnet/5.0/runtime
+ echo.
+ pause
+ exit
+)
+
+REM make sure an antivirus hasn't deleted the installer DLL
+if not exist "%installerDir%internal\windows\SMAPI.Installer.dll" (
+ echo Oops! SMAPI is missing one of its files. Your antivirus might have deleted it.
+ echo Missing file: %installerDir%internal\windows\SMAPI.Installer.dll
+ echo.
+ pause
+ exit
+)
+
+REM start installer
+dotnet internal\windows\SMAPI.Installer.dll
+
+REM keep window open if it failed
+if %ERRORLEVEL% NEQ 0 (
+ echo.
+ echo Oops! The SMAPI installer seems to have failed. The error details may be shown above.
+ echo.
+ pause
+ exit
)
diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
index 8cc61f44..264932e4 100644
--- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
+++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
@@ -1,18 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
-
<PropertyGroup>
- <TargetFramework>netcoreapp2.0</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="NUnit" Version="3.13.2" />
- <PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
+ <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup>
@@ -20,5 +16,4 @@
</ItemGroup>
<Import Project="..\..\build\common.targets" />
-
</Project>
diff --git a/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj b/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj
index 0d109b83..3fadc37a 100644
--- a/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj
+++ b/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj
@@ -9,8 +9,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" PrivateAssets="all" />
- <PackageReference Update="NETStandard.Library" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs
index 9ee6be12..140933bd 100644
--- a/src/SMAPI.ModBuildConfig/DeployModTask.cs
+++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs
@@ -8,6 +8,7 @@ using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using StardewModdingAPI.ModBuildConfig.Framework;
+using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.ModBuildConfig
{
@@ -17,6 +18,10 @@ namespace StardewModdingAPI.ModBuildConfig
/*********
** Accessors
*********/
+ /// <summary>The name (without extension or path) of the current mod's DLL.</summary>
+ [Required]
+ public string ModDllName { get; set; }
+
/// <summary>The name of the mod folder.</summary>
[Required]
public string ModFolderName { get; set; }
@@ -45,9 +50,15 @@ namespace StardewModdingAPI.ModBuildConfig
[Required]
public bool EnableModZip { get; set; }
- /// <summary>Custom comma-separated regex patterns matching files to ignore when deploying or zipping the mod.</summary>
+ /// <summary>A comma-separated list of regex patterns matching files to ignore when deploying or zipping the mod.</summary>
public string IgnoreModFilePatterns { get; set; }
+ /// <summary>A comma-separated list of relative file paths to ignore when deploying or zipping the mod.</summary>
+ public string IgnoreModFilePaths { get; set; }
+
+ /// <summary>A comma-separated list of <see cref="ExtraAssemblyTypes"/> values which indicate which extra DLLs to bundle.</summary>
+ public string BundleExtraAssemblies { get; set; }
+
/*********
** Public methods
@@ -69,11 +80,15 @@ namespace StardewModdingAPI.ModBuildConfig
try
{
+ // parse extra DLLs to bundle
+ ExtraAssemblyTypes bundleAssemblyTypes = this.GetExtraAssembliesToBundleOption();
+
// parse ignore patterns
+ string[] ignoreFilePaths = this.GetCustomIgnoreFilePaths().ToArray();
Regex[] ignoreFilePatterns = this.GetCustomIgnorePatterns().ToArray();
// get mod info
- ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePatterns, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip);
+ ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip);
// deploy mod files
if (this.EnableModDeploy)
@@ -134,6 +149,28 @@ namespace StardewModdingAPI.ModBuildConfig
}
}
+ /// <summary>Parse the extra assembly types which should be bundled with the mod.</summary>
+ private ExtraAssemblyTypes GetExtraAssembliesToBundleOption()
+ {
+ ExtraAssemblyTypes flags = ExtraAssemblyTypes.None;
+
+ if (!string.IsNullOrWhiteSpace(this.BundleExtraAssemblies))
+ {
+ foreach (string raw in this.BundleExtraAssemblies.Split(','))
+ {
+ if (!Enum.TryParse(raw, out ExtraAssemblyTypes type))
+ {
+ this.Log.LogWarning($"[mod build package] Ignored invalid <{nameof(this.BundleExtraAssemblies)}> value '{raw}', expected one of '{string.Join("', '", Enum.GetNames(typeof(ExtraAssemblyTypes)))}'.");
+ continue;
+ }
+
+ flags |= type;
+ }
+ }
+
+ return flags;
+ }
+
/// <summary>Get the custom ignore patterns provided by the user.</summary>
private IEnumerable<Regex> GetCustomIgnorePatterns()
{
@@ -157,6 +194,29 @@ namespace StardewModdingAPI.ModBuildConfig
}
}
+ /// <summary>Get the custom relative file paths provided by the user to ignore.</summary>
+ private IEnumerable<string> GetCustomIgnoreFilePaths()
+ {
+ if (string.IsNullOrWhiteSpace(this.IgnoreModFilePaths))
+ yield break;
+
+ foreach (string raw in this.IgnoreModFilePaths.Split(','))
+ {
+ string path;
+ try
+ {
+ path = PathUtilities.NormalizePath(raw);
+ }
+ catch (Exception ex)
+ {
+ this.Log.LogWarning($"[mod build package] Ignored invalid <{nameof(this.IgnoreModFilePaths)}> path {raw}:\n{ex}");
+ continue;
+ }
+
+ yield return path;
+ }
+ }
+
/// <summary>Copy the mod files into the game's mod folder.</summary>
/// <param name="files">The files to include.</param>
/// <param name="modFolderPath">The folder path to create with the mod files.</param>
diff --git a/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs b/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs
new file mode 100644
index 00000000..571bf7c7
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace StardewModdingAPI.ModBuildConfig.Framework
+{
+ /// <summary>An extra assembly type for the <see cref="DeployModTask.BundleExtraAssemblies"/> field.</summary>
+ [Flags]
+ internal enum ExtraAssemblyTypes
+ {
+ /// <summary>Don't include extra assemblies.</summary>
+ None = 0,
+
+ /// <summary>Assembly files which are part of MonoGame, SMAPI, or Stardew Valley.</summary>
+ Game = 1,
+
+ /// <summary>Assembly files whose names start with <c>Microsoft.*</c> or <c>System.*</c>.</summary>
+ System = 2,
+
+ /// <summary>Assembly files which don't match any other category.</summary>
+ ThirdParty = 4
+ }
+}
diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs
index 6dd595e5..ad4ffdf9 100644
--- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs
+++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs
@@ -21,6 +21,45 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
/// <summary>The files that are part of the package.</summary>
private readonly IDictionary<string, FileInfo> Files;
+ /// <summary>The file extensions used by assembly files.</summary>
+ private readonly ISet<string> AssemblyFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ ".dll",
+ ".exe",
+ ".pdb",
+ ".xml"
+ };
+
+ /// <summary>The DLLs which match the <see cref="ExtraAssemblyTypes.Game"/> type.</summary>
+ private readonly ISet<string> GameDllNames = new HashSet<string>
+ {
+ // SMAPI
+ "0Harmony",
+ "Mono.Cecil",
+ "Mono.Cecil.Mdb",
+ "Mono.Cecil.Pdb",
+ "MonoMod.Common",
+ "Newtonsoft.Json",
+ "StardewModdingAPI",
+ "SMAPI.Toolkit",
+ "SMAPI.Toolkit.CoreInterfaces",
+ "TMXTile",
+
+ // game + framework
+ "BmFont",
+ "FAudio-CS",
+ "GalaxyCSharp",
+ "GalaxyCSharpGlue",
+ "Lidgren.Network",
+ "MonoGame.Framework",
+ "SkiaSharp",
+ "Stardew Valley",
+ "StardewValley.GameData",
+ "Steamworks.NET",
+ "TextCopy",
+ "xTile"
+ };
+
/*********
** Public methods
@@ -28,10 +67,13 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
/// <summary>Construct an instance.</summary>
/// <param name="projectDir">The folder containing the project files.</param>
/// <param name="targetDir">The folder containing the build output.</param>
+ /// <param name="ignoreFilePaths">The custom relative file paths provided by the user to ignore.</param>
/// <param name="ignoreFilePatterns">Custom regex patterns matching files to ignore when deploying or zipping the mod.</param>
+ /// <param name="bundleAssemblyTypes">The extra assembly types which should be bundled with the mod.</param>
+ /// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
/// <param name="validateRequiredModFiles">Whether to validate that required mod files like the manifest are present.</param>
/// <exception cref="UserErrorException">The mod package isn't valid.</exception>
- public ModFileManager(string projectDir, string targetDir, Regex[] ignoreFilePatterns, bool validateRequiredModFiles)
+ public ModFileManager(string projectDir, string targetDir, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName, bool validateRequiredModFiles)
{
this.Files = new Dictionary<string, FileInfo>(StringComparer.OrdinalIgnoreCase);
@@ -47,7 +89,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
string relativePath = entry.Item1;
FileInfo file = entry.Item2;
- if (!this.ShouldIgnore(file, relativePath, ignoreFilePatterns))
+ if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
this.Files[relativePath] = file;
}
@@ -149,36 +191,72 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
/// <summary>Get whether a build output file should be ignored.</summary>
/// <param name="file">The file to check.</param>
/// <param name="relativePath">The file's relative path in the package.</param>
+ /// <param name="ignoreFilePaths">The custom relative file paths provided by the user to ignore.</param>
/// <param name="ignoreFilePatterns">Custom regex patterns matching files to ignore when deploying or zipping the mod.</param>
- private bool ShouldIgnore(FileInfo file, string relativePath, Regex[] ignoreFilePatterns)
+ /// <param name="bundleAssemblyTypes">The extra assembly types which should be bundled with the mod.</param>
+ /// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
+ private bool ShouldIgnore(FileInfo file, string relativePath, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName)
{
- return
- // release zips
- this.EqualsInvariant(file.Extension, ".zip")
+ // apply custom patterns
+ if (ignoreFilePaths.Any(p => p == relativePath) || ignoreFilePatterns.Any(p => p.IsMatch(relativePath)))
+ return true;
+
+ // ignore unneeded files
+ {
+ bool shouldIgnore =
+ // release zips
+ this.EqualsInvariant(file.Extension, ".zip")
+
+ // *.deps.json (only SMAPI's top-level one is used)
+ || file.Name.EndsWith(".deps.json")
+
+ // code analysis files
+ || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase)
+ || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase)
- // Harmony (bundled into SMAPI)
- || this.EqualsInvariant(file.Name, "0Harmony.dll")
+ // translation class builder (not used at runtime)
+ || (
+ file.Name.StartsWith("Pathoschild.Stardew.ModTranslationClassBuilder")
+ && this.AssemblyFileExtensions.Contains(file.Extension)
+ )
- // Json.NET (bundled into SMAPI)
- || this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll")
- || this.EqualsInvariant(file.Name, "Newtonsoft.Json.pdb")
- || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")
+ // OS metadata files
+ || this.EqualsInvariant(file.Name, ".DS_Store")
+ || this.EqualsInvariant(file.Name, "Thumbs.db");
+ if (shouldIgnore)
+ return true;
+ }
+
+ // check for bundled assembly types
+ // When bundleAssemblyTypes is set, *all* dependencies are copied into the build output but only those which match the given assembly types should be bundled.
+ if (bundleAssemblyTypes != ExtraAssemblyTypes.None)
+ {
+ var type = this.GetExtraAssemblyType(file, modDllName);
+ if (type != ExtraAssemblyTypes.None && !bundleAssemblyTypes.HasFlag(type))
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Get the extra assembly type for a file, assuming that the user specified one or more extra types to bundle.</summary>
+ /// <param name="file">The file to check.</param>
+ /// <param name="modDllName">The name (without extension or path) for the current mod's DLL.</param>
+ private ExtraAssemblyTypes GetExtraAssemblyType(FileInfo file, string modDllName)
+ {
+ string baseName = Path.GetFileNameWithoutExtension(file.Name);
+ string extension = file.Extension;
- // mod translation class builder (not used at runtime)
- || this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.dll")
- || this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.pdb")
- || this.EqualsInvariant(file.Name, "Pathoschild.Stardew.ModTranslationClassBuilder.xml")
+ if (baseName == modDllName || !this.AssemblyFileExtensions.Contains(extension))
+ return ExtraAssemblyTypes.None;
- // code analysis files
- || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase)
- || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase)
+ if (this.GameDllNames.Contains(baseName))
+ return ExtraAssemblyTypes.Game;
- // OS metadata files
- || this.EqualsInvariant(file.Name, ".DS_Store")
- || this.EqualsInvariant(file.Name, "Thumbs.db")
+ if (baseName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) || baseName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase))
+ return ExtraAssemblyTypes.System;
- // custom ignore patterns
- || ignoreFilePatterns.Any(p => p.IsMatch(relativePath));
+ return ExtraAssemblyTypes.ThirdParty;
}
/// <summary>Get whether a string is equal to another case-insensitively.</summary>
diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
index 93769650..0bc8c45e 100644
--- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
+++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
@@ -2,28 +2,28 @@
<PropertyGroup>
<!--build-->
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!--NuGet package-->
<PackageId>Pathoschild.Stardew.ModBuildConfig</PackageId>
<Title>Build package for SMAPI mods</Title>
- <Version>3.3.0</Version>
+ <Version>4.0.0</Version>
<Authors>Pathoschild</Authors>
- <Description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.0 or later.</Description>
+ <Description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.13.0 or later.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>images/icon.png</PackageIcon>
<PackageProjectUrl>https://smapi.io/package/readme</PackageProjectUrl>
<IncludeBuildOutput>false</IncludeBuildOutput>
+
+ <!--copy dependency DLLs to bin folder so we can include them in package -->
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
- <Reference Include="Microsoft.Build" />
- <Reference Include="Microsoft.Build.Framework" />
- <Reference Include="Microsoft.Build.Utilities.v4.0" />
- <Reference Include="System.IO.Compression" />
- <Reference Include="System.Web.Extensions" />
+ <PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets
index 698765ad..b66ec27b 100644
--- a/src/SMAPI.ModBuildConfig/build/smapi.targets
+++ b/src/SMAPI.ModBuildConfig/build/smapi.targets
@@ -12,8 +12,8 @@
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
- <!-- recognise XNA Framework DLLs in the GAC (only affects mods using new csproj format) -->
- <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
+ <!-- don't create the 'refs' folder (which isn't useful for mods) -->
+ <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<!-- suppress processor architecture mismatch warning (mods should be compiled in 'Any CPU' so they work in both 32-bit and 64-bit mode) -->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
@@ -26,10 +26,10 @@
<EnableModZip Condition="'$(EnableModZip)' == ''">true</EnableModZip>
<EnableHarmony Condition="'$(EnableHarmony)' == ''">false</EnableHarmony>
<EnableGameDebugging Condition="'$(EnableGameDebugging)' == ''">true</EnableGameDebugging>
- <CopyModReferencesToBuildOutput Condition="'$(CopyModReferencesToBuildOutput)' == '' OR ('$(CopyModReferencesToBuildOutput)' != 'true' AND '$(CopyModReferencesToBuildOutput)' != 'false')">false</CopyModReferencesToBuildOutput>
+ <BundleExtraAssemblies Condition="'$(BundleExtraAssemblies)' == ''"></BundleExtraAssemblies>
- <GameFramework Condition="'$(GameFramework)' == '' AND '$(OS)' == 'Windows_NT'">Xna</GameFramework>
- <GameFramework Condition="'$(GameFramework)' == ''">MonoGame</GameFramework>
+ <!-- coppy referenced DLLs into build output -->
+ <CopyLocalLockFileAssemblies Condition="$(BundleExtraAssemblies.Contains('ThirdParty')) OR $(BundleExtraAssemblies.Contains('Game')) OR $(BundleExtraAssemblies.Contains('System')) OR $(BundleExtraAssemblies.Contains('All'))">true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' == 'Windows_NT' AND '$(EnableGameDebugging)' == 'true'">
@@ -43,38 +43,21 @@
<!--*********************************************
** Add assembly references
**********************************************-->
- <!-- common -->
<ItemGroup>
- <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.exe" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(CopyModReferencesToBuildOutput)" />
+ <!-- game -->
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+ <Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+ <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+ <Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+
+ <!-- SMAPI -->
+ <Reference Include="StardewModdingAPI" HintPath="$(GamePath)\StardewModdingAPI.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+ <Reference Include="SMAPI.Toolkit.CoreInterfaces" HintPath="$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
+
+ <!-- Harmony -->
+ <Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'" HintPath="$(GamePath)\smapi-internal\0Harmony.dll" Private="$(BundleExtraAssemblies.Contains('Game'))" />
</ItemGroup>
- <!-- Windows only -->
- <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
- <Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="$(CopyModReferencesToBuildOutput)" />
- </ItemGroup>
-
- <!-- Game framework -->
- <Choose>
- <When Condition="'$(GameFramework)' == 'Xna'">
- <ItemGroup>
- <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
- <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" />
- </ItemGroup>
- </When>
- <Otherwise>
- <ItemGroup>
- <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(CopyModReferencesToBuildOutput)" />
- </ItemGroup>
- </Otherwise>
- </Choose>
-
<!--*********************************************
** Show validation messages
@@ -85,8 +68,8 @@
<!-- invalid game path -->
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see https://smapi.io/package/custom-game-path." ContinueOnError="false" />
- <Error Condition="!Exists('$(GamePath)\$(GameExecutableName).exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the $(GameExecutableName) file. If this folder is invalid, delete it and the package will autodetect another game install path." ContinueOnError="false" />
- <Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." ContinueOnError="false" />
+ <Error Condition="!Exists('$(GamePath)\Stardew Valley.dll')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the Stardew Valley file. If this folder is invalid, delete it and the package will autodetect another game install path." ContinueOnError="false" />
+ <Error Condition="!Exists('$(GamePath)\StardewModdingAPI.dll')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." ContinueOnError="false" />
<!-- invalid target architecture (note: internal value is 'AnyCPU', value shown in Visual Studio is 'Any CPU') -->
<Warning Condition="'$(Platform)' != 'AnyCPU'" Text="The target platform should be set to 'Any CPU' for compatibility with both 32-bit and 64-bit versions of Stardew Valley (currently set to '$(Platform)'). See https://smapi.io/package/wrong-processor-architecture for details." HelpLink="https://smapi.io/package/wrong-processor-architecture" />
@@ -98,6 +81,7 @@
**********************************************-->
<Target Name="AfterBuild">
<DeployModTask
+ ModDllName="$(TargetName)"
ModFolderName="$(ModFolderName)"
ModZipPath="$(ModZipPath)"
@@ -108,6 +92,9 @@
TargetDir="$(TargetDir)"
GameModsDir="$(GameModsPath)"
IgnoreModFilePatterns="$(IgnoreModFilePatterns)"
+ IgnoreModFilePaths="$(IgnoreModFilePaths)"
+
+ BundleExtraAssemblies="$(BundleExtraAssemblies)"
/>
</Target>
</Project>
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs
new file mode 100644
index 00000000..6b3b27cd
--- /dev/null
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using StardewValley;
+using StardewValley.GameData;
+
+namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
+{
+ /// <summary>A command which changes the player's farm type.</summary>
+ internal class SetFarmTypeCommand : ConsoleCommand
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public SetFarmTypeCommand()
+ : base("set_farm_type", "Sets the current player's farm type.\n\nUsage: set_farm_type <farm type>\n- farm type: the farm type to set. Enter `set_farm_type list` for a list of available farm types.") { }
+
+ /// <summary>Handle the command.</summary>
+ /// <param name="monitor">Writes messages to the console and log file.</param>
+ /// <param name="command">The command name.</param>
+ /// <param name="args">The command arguments.</param>
+ public override void Handle(IMonitor monitor, string command, ArgumentParser args)
+ {
+ // validate
+ if (!Context.IsWorldReady)
+ {
+ monitor.Log("You must load a save to use this command.", LogLevel.Error);
+ return;
+ }
+
+ // parse arguments
+ if (!args.TryGet(0, "farm type", out string farmType))
+ return;
+ bool isVanillaId = int.TryParse(farmType, out int vanillaId) && vanillaId is (>= 0 and < Farm.layout_max);
+
+ // handle argument
+ if (farmType == "list")
+ this.HandleList(monitor);
+ else if (isVanillaId)
+ this.HandleVanillaFarmType(vanillaId, monitor);
+ else
+ this.HandleCustomFarmType(farmType, monitor);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /****
+ ** Handlers
+ ****/
+ /// <summary>Print a list of available farm types.</summary>
+ /// <param name="monitor">Writes messages to the console and log file.</param>
+ private void HandleList(IMonitor monitor)
+ {
+ StringBuilder result = new();
+
+ // list vanilla types
+ result.AppendLine("The farm type can be one of these vanilla types:");
+ foreach (var type in this.GetVanillaFarmTypes())
+ result.AppendLine($" - {type.Key} ({type.Value})");
+ result.AppendLine();
+
+ // list custom types
+ {
+ var customTypes = this.GetCustomFarmTypes();
+ if (customTypes.Any())
+ {
+ result.AppendLine("Or one of these custom farm types:");
+ foreach (var type in customTypes.Values.OrderBy(p => p.ID))
+ result.AppendLine($" - {type.ID} ({this.GetCustomName(type)})");
+ }
+ else
+ result.AppendLine("Or a custom farm type (though none is loaded currently).");
+ }
+
+ // print
+ monitor.Log(result.ToString(), LogLevel.Info);
+ }
+
+ /// <summary>Set a vanilla farm type.</summary>
+ /// <param name="type">The farm type.</param>
+ /// <param name="monitor">Writes messages to the console and log file.</param>
+ private void HandleVanillaFarmType(int type, IMonitor monitor)
+ {
+ if (Game1.whichFarm == type)
+ {
+ monitor.Log($"Your current farm is already set to {type} ({this.GetVanillaName(type)}).", LogLevel.Info);
+ return;
+ }
+
+ this.SetFarmType(type, null);
+ this.PrintSuccess(monitor, $"{type} ({this.GetVanillaName(type)}");
+ }
+
+ /// <summary>Set a custom farm type.</summary>
+ /// <param name="id">The farm type ID.</param>
+ /// <param name="monitor">Writes messages to the console and log file.</param>
+ private void HandleCustomFarmType(string id, IMonitor monitor)
+ {
+ if (Game1.whichModFarm?.ID == id)
+ {
+ monitor.Log($"Your current farm is already set to {id} ({this.GetCustomName(Game1.whichModFarm)}).", LogLevel.Info);
+ return;
+ }
+
+ if (!this.GetCustomFarmTypes().TryGetValue(id, out ModFarmType customFarmType))
+ {
+ monitor.Log($"Invalid farm type '{id}'. Enter `help set_farm_type` for more info.", LogLevel.Error);
+ return;
+ }
+
+ this.SetFarmType(Farm.mod_layout, customFarmType);
+ this.PrintSuccess(monitor, $"{id} ({this.GetCustomName(customFarmType)})");
+ }
+
+ /// <summary>Change the farm type.</summary>
+ /// <param name="type">The farm type ID.</param>
+ /// <param name="customFarmData">The custom farm type data, if applicable.</param>
+ private void SetFarmType(int type, ModFarmType customFarmData)
+ {
+ // set flags
+ Game1.whichFarm = type;
+ Game1.whichModFarm = customFarmData;
+
+ // update farm map
+ Farm farm = Game1.getFarm();
+ farm.mapPath.Value = $@"Maps\{Farm.getMapNameFromTypeInt(Game1.whichFarm)}";
+ farm.reloadMap();
+
+ // clear spouse area cache to avoid errors
+ FieldInfo cacheField = farm.GetType().GetField("_baseSpouseAreaTiles", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ if (cacheField == null)
+ throw new InvalidOperationException("Failed to access '_baseSpouseAreaTiles' field to clear spouse area cache.");
+ if (cacheField.GetValue(farm) is not IDictionary cache)
+ throw new InvalidOperationException($"The farm's '_baseSpouseAreaTiles' field didn't match the expected {nameof(IDictionary)} type.");
+ cache.Clear();
+ }
+
+ private void PrintSuccess(IMonitor monitor, string label)
+ {
+ StringBuilder result = new();
+ result.AppendLine($"Your current farm has been converted to {label}. Saving and reloading is recommended to make sure everything is updated for the change.");
+ result.AppendLine();
+ result.AppendLine("This doesn't move items that are out of bounds on the new map. If you need to clean up, you can...");
+ result.AppendLine(" - temporarily switch back to the previous farm type;");
+ result.AppendLine(" - or use a mod like Noclip Mode: https://www.nexusmods.com/stardewvalley/mods/3900 ;");
+ result.AppendLine(" - or use the world_clear console command (enter `help world_clear` for details).");
+
+ monitor.Log(result.ToString(), LogLevel.Warn);
+ }
+
+ /****
+ ** Vanilla farm types
+ ****/
+ /// <summary>Get the display name for a vanilla farm type.</summary>
+ /// <param name="type">The farm type.</param>
+ private string GetVanillaName(int type)
+ {
+ string translationKey = type switch
+ {
+ Farm.default_layout => "Character_FarmStandard",
+ Farm.riverlands_layout => "Character_FarmFishing",
+ Farm.forest_layout => "Character_FarmForaging",
+ Farm.mountains_layout => "Character_FarmMining",
+ Farm.combat_layout => "Character_FarmCombat",
+ Farm.fourCorners_layout => "Character_FarmFourCorners",
+ Farm.beach_layout => "Character_FarmBeach",
+ _ => null
+ };
+
+ return translationKey != null
+ ? Game1.content.LoadString(@$"Strings\UI:{translationKey}").Split('_')[0]
+ : type.ToString();
+ }
+
+ /// <summary>Get the available vanilla farm types by ID.</summary>
+ private IDictionary<int, string> GetVanillaFarmTypes()
+ {
+ IDictionary<int, string> farmTypes = new Dictionary<int, string>();
+
+ foreach (int id in Enumerable.Range(0, Farm.layout_max))
+ farmTypes[id] = this.GetVanillaName(id);
+
+ return farmTypes;
+ }
+
+ /****
+ ** Custom farm types
+ ****/
+ /// <summary>Get the display name for a custom farm type.</summary>
+ /// <param name="farmType">The custom farm type.</param>
+ private string GetCustomName(ModFarmType farmType)
+ {
+ if (string.IsNullOrWhiteSpace(farmType?.TooltipStringPath))
+ return farmType?.ID;
+
+ return Game1.content.LoadString(farmType.TooltipStringPath)?.Split('_')[0] ?? farmType.ID;
+ }
+
+ /// <summary>Get the available custom farm types by ID.</summary>
+ private IDictionary<string, ModFarmType> GetCustomFarmTypes()
+ {
+ IDictionary<string, ModFarmType> farmTypes = new Dictionary<string, ModFarmType>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (ModFarmType farmType in Game1.content.Load<List<ModFarmType>>("Data\\AdditionalFarms"))
+ {
+ if (string.IsNullOrWhiteSpace(farmType.ID))
+ continue;
+
+ farmTypes[farmType.ID] = farmType;
+ }
+
+ return farmTypes;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
index 528348a0..e3db8b47 100644
--- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
+++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ConsoleCommands</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
@@ -13,32 +13,12 @@
</ItemGroup>
<ItemGroup>
- <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
+ <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
+ <Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
</ItemGroup>
- <!-- Windows only -->
- <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
- <Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
- </ItemGroup>
-
- <!-- Game framework -->
- <Choose>
- <When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
- <ItemGroup>
- <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- </ItemGroup>
- </When>
- <Otherwise>
- <ItemGroup>
- <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
- </ItemGroup>
- </Otherwise>
- </Choose>
-
<ItemGroup>
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 9af3ce5d..85653a7d 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.12.8",
+ "Version": "3.13.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.12.8"
+ "MinimumApiVersion": "3.13.0"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs
index 6860a4ec..f243c6d1 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs
@@ -19,9 +19,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
public override void Apply(Harmony harmony, IMonitor monitor)
{
harmony.Patch(
- original: Constants.GameFramework == GameFramework.Xna
- ? this.RequireMethod<SpriteBatch>("InternalDraw")
- : this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
+ original: this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
postfix: this.GetHarmonyMethod(nameof(SpriteBatchPatcher.After_CheckValid))
);
}
@@ -30,13 +28,8 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
** Private methods
*********/
-#if SMAPI_FOR_XNA
- /// <summary>The method to call after <see cref="SpriteBatch.InternalDraw"/>.</summary>
- /// <param name="texture">The texture to validate.</param>
-#else
/// <summary>The method to call after <see cref="SpriteBatch.CheckValid"/>.</summary>
/// <param name="texture">The texture to validate.</param>
-#endif
private static void After_CheckValid(Texture2D texture)
{
if (texture?.IsDisposed == true)
diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
index 182a978e..78cdb315 100644
--- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
+++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ErrorHandler</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
@@ -14,33 +14,12 @@
</ItemGroup>
<ItemGroup>
- <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
+ <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
</ItemGroup>
- <!-- Windows only -->
- <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
- <Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
- </ItemGroup>
-
- <!-- Game framework -->
- <Choose>
- <When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
- <ItemGroup>
- <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- </ItemGroup>
- </When>
- <Otherwise>
- <ItemGroup>
- <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
- </ItemGroup>
- </Otherwise>
- </Choose>
-
<ItemGroup>
<None Update="i18n\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index cfbaa661..da8306d1 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.12.8",
+ "Version": "3.13.0",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.12.8"
+ "MinimumApiVersion": "3.13.0"
}
diff --git a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
index 079beb08..a8b0dfdb 100644
--- a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
+++ b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SaveBackup</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
- <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 1efd1d89..4c57bd49 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.12.8",
+ "Version": "3.13.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.12.8"
+ "MinimumApiVersion": "3.13.0"
}
diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj
index 8f7bfab4..8329b2e1 100644
--- a/src/SMAPI.Tests/SMAPI.Tests.csproj
+++ b/src/SMAPI.Tests/SMAPI.Tests.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SMAPI.Tests</AssemblyName>
<RootNamespace>SMAPI.Tests</RootNamespace>
- <TargetFramework>net452</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>latest</LangVersion>
</PropertyGroup>
@@ -16,16 +16,14 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
</ItemGroup>
<ItemGroup>
- <Reference Include="$(GameExecutableName)">
- <HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
- <Private>True</Private>
- </Reference>
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="True" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
index c18f47a5..ab4c2618 100644
--- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
+++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
@@ -182,18 +182,14 @@ namespace SMAPI.Tests.Utilities
[TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))]
public void NormalizeAssetName(SamplePath path)
{
- if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith("/") || path.OriginalPath.StartsWith("\\"))
+ if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith('/') || path.OriginalPath.StartsWith('\\'))
Assert.Ignore("Absolute paths can't be used as asset names.");
// act
string normalized = PathUtilities.NormalizeAssetName(path.OriginalPath);
// assert
-#if SMAPI_FOR_WINDOWS
- Assert.AreEqual(path.NormalizedOnWindows, normalized);
-#else
- Assert.AreEqual(path.NormalizedOnUnix, normalized);
-#endif
+ Assert.AreEqual(path.NormalizedOnUnix, normalized); // MonoGame uses the Linux format
}
/****
diff --git a/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj b/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
index 0e1e40b2..4c92b4db 100644
--- a/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
+++ b/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI</RootNamespace>
<Description>Provides toolkit interfaces which are available to SMAPI mods.</Description>
- <TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
+ <TargetFrameworks>net5.0; netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/SMAPI.Toolkit/Framework/Constants.cs b/src/SMAPI.Toolkit/Framework/Constants.cs
new file mode 100644
index 00000000..55f26582
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Constants.cs
@@ -0,0 +1,9 @@
+namespace StardewModdingAPI.Toolkit.Framework
+{
+ /// <summary>Contains the SMAPI installer's constants and assumptions.</summary>
+ internal static class Constants
+ {
+ /// <summary>The name of the game's main DLL, used to detect game folders.</summary>
+ public const string GameDllName = "Stardew Valley.dll";
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
index c90fc1d3..7553c07f 100644
--- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
@@ -56,10 +56,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
{
return
dir.Exists
- && (
- dir.EnumerateFiles("StardewValley.exe").Any()
- || dir.EnumerateFiles("Stardew Valley.exe").Any()
- );
+ && dir.EnumerateFiles("Stardew Valley.dll").Any();
}
diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
index 2636aae0..8b6eb5fb 100644
--- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
+++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
@@ -80,15 +80,6 @@ namespace StardewModdingAPI.Toolkit.Framework
return name;
}
- /// <summary>Get the name of the Stardew Valley executable.</summary>
- /// <param name="platform">The current platform.</param>
- public static string GetExecutableName(string platform)
- {
- return platform == nameof(Platform.Windows)
- ? "Stardew Valley.exe"
- : "StardewValley.exe";
- }
-
/// <summary>Get whether an executable is 64-bit.</summary>
/// <param name="path">The absolute path to the assembly file.</param>
public static bool Is64BitAssembly(string path)
diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
index 3d87c169..ec27bf79 100644
--- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
+++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
<Description>A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.</Description>
- <TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
+ <TargetFrameworks>net5.0; netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -12,8 +12,8 @@
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
- <PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" />
- <PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT' AND '$(TargetFramework)' == 'netstandard2.0'" />
+ <PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
+ <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
index 6de79a85..7536337a 100644
--- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
+++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
@@ -40,13 +40,6 @@ namespace StardewModdingAPI.Toolkit.Utilities
return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString());
}
- /// <summary>Get the name of the Stardew Valley executable.</summary>
- /// <param name="platform">The current platform.</param>
- public static string GetExecutableName(Platform platform)
- {
- return LowLevelEnvironmentUtility.GetExecutableName(platform.ToString());
- }
-
/// <summary>Get whether an executable is 64-bit.</summary>
/// <param name="path">The absolute path to the assembly file.</param>
public static bool Is64BitAssembly(string path)
diff --git a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
index 7856fdb1..a6bf5929 100644
--- a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
+++ b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
@@ -1,4 +1,6 @@
+using System;
using System.IO;
+using System.Security.Cryptography;
using System.Threading;
namespace StardewModdingAPI.Toolkit.Utilities
@@ -42,5 +44,16 @@ namespace StardewModdingAPI.Toolkit.Utilities
if (entry.Exists)
throw new IOException($"Timed out trying to delete {entry.FullName}");
}
+
+ /// <summary>Get the MD5 hash for a file.</summary>
+ /// <param name="absolutePath">The absolute file path.</param>
+ public static string GetFileHash(string absolutePath)
+ {
+ using FileStream stream = File.OpenRead(absolutePath);
+ using MD5 md5 = MD5.Create();
+
+ byte[] hash = md5.ComputeHash(stream);
+ return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
index 020ebc6d..2e9e5eac 100644
--- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
+++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
@@ -27,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
public static readonly char PreferredPathSeparator = Path.DirectorySeparatorChar;
/// <summary>The preferred directory separator character in an asset key.</summary>
- public static readonly char PreferredAssetSeparator = PathUtilities.PreferredPathSeparator;
+ public static readonly char PreferredAssetSeparator = '/';
/*********
@@ -88,14 +87,18 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <summary>Get a directory or file path relative to a given source path. If no relative path is possible (e.g. the paths are on different drives), an absolute path is returned.</summary>
/// <param name="sourceDir">The source folder path.</param>
/// <param name="targetPath">The target folder or file path.</param>
- /// <remarks>
- ///
- /// NOTE: this is a heuristic implementation that works in the cases SMAPI needs it for, but it doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between UNC paths on Windows). This should be replaced with the more comprehensive <c>Path.GetRelativePath</c> if the game ever migrates to .NET Core.
- ///
- /// </remarks>
[Pure]
public static string GetRelativePath(string sourceDir, string targetPath)
{
+#if NET5_0
+ return Path.GetRelativePath(sourceDir, targetPath);
+#else
+ // NOTE:
+ // this is a heuristic implementation that works in the cases SMAPI needs it for, but it
+ // doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between
+ // UNC paths on Windows). SMAPI and mods will use the more robust .NET 5 version anyway
+ // though, this is only for compatibility with the mod build package.
+
// convert to URIs
Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
@@ -123,6 +126,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
}
return relative;
+#endif
}
/// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
@@ -145,32 +149,5 @@ namespace StardewModdingAPI.Toolkit.Utilities
{
return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
}
-
- /// <summary>Get the paths which exceed the OS length limit.</summary>
- /// <param name="rootPath">The root path to search.</param>
- internal static IEnumerable<string> GetTooLongPaths(string rootPath)
- {
- if (!Directory.Exists(rootPath))
- return new string[0];
-
- return Directory
- .EnumerateFileSystemEntries(rootPath, "*.*", SearchOption.AllDirectories)
- .Where(PathUtilities.IsPathTooLong);
- }
-
- /// <summary>Get whether a file or directory path exceeds the OS path length limit.</summary>
- /// <param name="path">The path to test.</param>
- internal static bool IsPathTooLong(string path)
- {
- try
- {
- _ = Path.GetFullPath(path);
- return false;
- }
- catch (PathTooLongException)
- {
- return true;
- }
- }
}
}
diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs
index 080285ab..f2f4c342 100644
--- a/src/SMAPI.Web/Controllers/IndexController.cs
+++ b/src/SMAPI.Web/Controllers/IndexController.cs
@@ -57,21 +57,16 @@ namespace StardewModdingAPI.Web.Controllers
{
// choose versions
ReleaseVersion[] versions = await this.GetReleaseVersionsAsync();
- ReleaseVersion stableVersion = versions.LastOrDefault(version => !version.IsBeta && !version.IsForDevs);
- ReleaseVersion stableVersionForDevs = versions.LastOrDefault(version => !version.IsBeta && version.IsForDevs);
- ReleaseVersion betaVersion = versions.LastOrDefault(version => version.IsBeta && !version.IsForDevs);
- ReleaseVersion betaVersionForDevs = versions.LastOrDefault(version => version.IsBeta && version.IsForDevs);
+ ReleaseVersion stableVersion = versions.LastOrDefault(version => !version.IsForDevs);
+ ReleaseVersion stableVersionForDevs = versions.LastOrDefault(version => version.IsForDevs);
// render view
IndexVersionModel stableVersionModel = stableVersion != null
? new IndexVersionModel(stableVersion.Version.ToString(), stableVersion.Release.Body, stableVersion.Asset.DownloadUrl, stableVersionForDevs?.Asset.DownloadUrl)
- : new IndexVersionModel("unknown", "", "https://github.com/Pathoschild/SMAPI/releases", null); // just in case something goes wrong)
- IndexVersionModel betaVersionModel = betaVersion != null && this.SiteConfig.BetaEnabled
- ? new IndexVersionModel(betaVersion.Version.ToString(), betaVersion.Release.Body, betaVersion.Asset.DownloadUrl, betaVersionForDevs?.Asset.DownloadUrl)
- : null;
+ : new IndexVersionModel("unknown", "", "https://github.com/Pathoschild/SMAPI/releases", null); // just in case something goes wrong
// render view
- var model = new IndexModel(stableVersionModel, betaVersionModel, this.SiteConfig.BetaBlurb, this.SiteConfig.SupporterList);
+ var model = new IndexModel(stableVersionModel, this.SiteConfig.OtherBlurb, this.SiteConfig.SupporterList);
return this.View(model);
}
@@ -93,27 +88,12 @@ namespace StardewModdingAPI.Web.Controllers
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime);
- // get latest release (whether preview or stable)
- GitRelease stableRelease = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: true);
+ // get latest stable release
+ GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false);
- // split stable/prerelease if applicable
- GitRelease betaRelease = null;
- if (stableRelease.IsPrerelease)
+ // strip 'noinclude' blocks from release description
+ if (release != null)
{
- GitRelease result = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false);
- if (result != null)
- {
- betaRelease = stableRelease;
- stableRelease = result;
- }
- }
-
- // strip 'noinclude' blocks from release descriptions
- foreach (GitRelease release in new[] { stableRelease, betaRelease })
- {
- if (release == null)
- continue;
-
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(release.Body);
foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//*[@class='noinclude']")?.ToArray() ?? new HtmlNode[0])
@@ -122,10 +102,8 @@ namespace StardewModdingAPI.Web.Controllers
}
// get versions
- ReleaseVersion[] stableVersions = this.ParseReleaseVersions(stableRelease).ToArray();
- ReleaseVersion[] betaVersions = this.ParseReleaseVersions(betaRelease).ToArray();
- return stableVersions
- .Concat(betaVersions)
+ return this
+ .ParseReleaseVersions(release)
.OrderBy(p => p.Version)
.ToArray();
});
@@ -146,10 +124,9 @@ namespace StardewModdingAPI.Web.Controllers
Match match = Regex.Match(asset.FileName, @"SMAPI-(?<version>[\d\.]+(?:-.+)?)-installer(?<forDevs>-for-developers)?.zip");
if (!match.Success || !SemanticVersion.TryParse(match.Groups["version"].Value, out ISemanticVersion version))
continue;
- bool isBeta = version.IsPrerelease();
bool isForDevs = match.Groups["forDevs"].Success;
- yield return new ReleaseVersion(release, asset, version, isBeta, isForDevs);
+ yield return new ReleaseVersion(release, asset, version, isForDevs);
}
}
@@ -168,9 +145,6 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>The SMAPI version.</summary>
public ISemanticVersion Version { get; }
- /// <summary>Whether this is a beta download.</summary>
- public bool IsBeta { get; }
-
/// <summary>Whether this is a 'for developers' download.</summary>
public bool IsForDevs { get; }
@@ -182,14 +156,12 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="release">The underlying GitHub release.</param>
/// <param name="asset">The underlying download asset.</param>
/// <param name="version">The SMAPI version.</param>
- /// <param name="isBeta">Whether this is a beta download.</param>
/// <param name="isForDevs">Whether this is a 'for developers' download.</param>
- public ReleaseVersion(GitRelease release, GitAsset asset, ISemanticVersion version, bool isBeta, bool isForDevs)
+ public ReleaseVersion(GitRelease release, GitAsset asset, ISemanticVersion version, bool isForDevs)
{
this.Release = release;
this.Asset = asset;
this.Version = version;
- this.IsBeta = isBeta;
this.IsForDevs = isForDevs;
}
}
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index dcddaf10..37d763cc 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -81,6 +81,8 @@ namespace StardewModdingAPI.Web.Controllers
if (model?.Mods == null)
return new ModEntryModel[0];
+ ModUpdateCheckConfig config = this.Config.Value;
+
// fetch wiki data
WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray();
IDictionary<string, ModEntryModel> mods = new Dictionary<string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase);
@@ -89,6 +91,11 @@ namespace StardewModdingAPI.Web.Controllers
if (string.IsNullOrWhiteSpace(mod.ID))
continue;
+ // special case: if this is an update check for the official SMAPI repo, check the Nexus mod page for beta versions
+ if (mod.ID == config.SmapiInfo.ID && mod.UpdateKeys?.Any(key => key == config.SmapiInfo.DefaultUpdateKey) == true && mod.InstalledVersion?.IsPrerelease() == true)
+ mod.UpdateKeys = mod.UpdateKeys.Concat(config.SmapiInfo.AddBetaUpdateKeys).ToArray();
+
+ // fetch result
ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata, model.ApiVersion);
if (!model.IncludeExtendedMetadata && (model.ApiVersion == null || mod.InstalledVersion == null))
{
diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
index bd58dba0..aea695b8 100644
--- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
+++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
@@ -14,5 +14,8 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/// <summary>Update-check metadata to override.</summary>
public ModOverrideConfig[] ModOverrides { get; set; }
+
+ /// <summary>The update-check config for SMAPI's own update checks.</summary>
+ public SmapiInfoConfig SmapiInfo { get; set; }
}
}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs
index 43969f51..664dbef3 100644
--- a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs
+++ b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs
@@ -6,11 +6,8 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/*********
** Accessors
*********/
- /// <summary>Whether to show SMAPI beta versions on the main page, if any.</summary>
- public bool BetaEnabled { get; set; }
-
- /// <summary>A short sentence shown under the beta download button, if any.</summary>
- public string BetaBlurb { get; set; }
+ /// <summary>A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</summary>
+ public string OtherBlurb { get; set; }
/// <summary>A list of supports to credit on the main page, in Markdown format.</summary>
public string SupporterList { get; set; }
diff --git a/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs
new file mode 100644
index 00000000..d69fabb3
--- /dev/null
+++ b/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.ConfigModels
+{
+ /// <summary>The update-check config for SMAPI's own update checks.</summary>
+ internal class SmapiInfoConfig
+ {
+ /// <summary>The mod ID used for SMAPI update checks.</summary>
+ public string ID { get; set; }
+
+ /// <summary>The default update key used for SMAPI update checks.</summary>
+ public string DefaultUpdateKey { get; set; }
+
+ /// <summary>The update keys to add for SMAPI update checks when the player has a beta version installed.</summary>
+ public string[] AddBetaUpdateKeys { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj
index 4c8465a6..f1400e62 100644
--- a/src/SMAPI.Web/SMAPI.Web.csproj
+++ b/src/SMAPI.Web/SMAPI.Web.csproj
@@ -2,27 +2,29 @@
<PropertyGroup>
<AssemblyName>SMAPI.Web</AssemblyName>
<RootNamespace>StardewModdingAPI.Web</RootNamespace>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<Import Project="..\..\build\common.targets" />
<ItemGroup>
+ <None Remove="Properties\PublishProfiles\**" />
+ <None Remove="Properties\ServiceDependencies\**" />
<Content Remove="aws-beanstalk-tools-defaults.json" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Azure.Storage.Blobs" Version="12.8.3" />
- <PackageReference Include="Hangfire.AspNetCore" Version="1.7.22" />
+ <PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
+ <PackageReference Include="Hangfire.AspNetCore" Version="1.7.27" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
- <PackageReference Include="Humanizer.Core" Version="2.9.9" />
- <PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
- <PackageReference Include="Markdig" Version="0.24.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.8" />
+ <PackageReference Include="Humanizer.Core" Version="2.13.14" />
+ <PackageReference Include="JetBrains.Annotations" Version="2021.3.0" />
+ <PackageReference Include="Markdig" Version="0.26.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
- <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.2" />
+ <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.Web/ViewModels/IndexModel.cs b/src/SMAPI.Web/ViewModels/IndexModel.cs
index 450fdc0e..d8d2d27f 100644
--- a/src/SMAPI.Web/ViewModels/IndexModel.cs
+++ b/src/SMAPI.Web/ViewModels/IndexModel.cs
@@ -9,11 +9,8 @@ namespace StardewModdingAPI.Web.ViewModels
/// <summary>The latest stable SMAPI version.</summary>
public IndexVersionModel StableVersion { get; set; }
- /// <summary>The latest prerelease SMAPI version (if newer than <see cref="StableVersion"/>).</summary>
- public IndexVersionModel BetaVersion { get; set; }
-
- /// <summary>A short sentence shown under the beta download button, if any.</summary>
- public string BetaBlurb { get; set; }
+ /// <summary>A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</summary>
+ public string OtherBlurb { get; set; }
/// <summary>A list of supports to credit on the main page, in Markdown format.</summary>
public string SupporterList { get; set; }
@@ -27,14 +24,12 @@ namespace StardewModdingAPI.Web.ViewModels
/// <summary>Construct an instance.</summary>
/// <param name="stableVersion">The latest stable SMAPI version.</param>
- /// <param name="betaVersion">The latest prerelease SMAPI version (if newer than <paramref name="stableVersion"/>).</param>
- /// <param name="betaBlurb">A short sentence shown under the beta download button, if any.</param>
+ /// <param name="otherBlurb">A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</param>
/// <param name="supporterList">A list of supports to credit on the main page, in Markdown format.</param>
- internal IndexModel(IndexVersionModel stableVersion, IndexVersionModel betaVersion, string betaBlurb, string supporterList)
+ internal IndexModel(IndexVersionModel stableVersion, string otherBlurb, string supporterList)
{
this.StableVersion = stableVersion;
- this.BetaVersion = betaVersion;
- this.BetaBlurb = betaBlurb;
+ this.OtherBlurb = otherBlurb;
this.SupporterList = supporterList;
}
}
diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml
index 9d6e4bed..669cfd99 100644
--- a/src/SMAPI.Web/Views/Index/Index.cshtml
+++ b/src/SMAPI.Web/Views/Index/Index.cshtml
@@ -8,9 +8,9 @@
ViewData["ViewTitle"] = string.Empty;
}
@section Head {
- <link rel="stylesheet" href="~/Content/css/index.css?r=20200105" />
+ <link rel="stylesheet" href="~/Content/css/index.css" />
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1" crossorigin="anonymous"></script>
- <script src="~/Content/js/index.js?r=20200105"></script>
+ <script src="~/Content/js/index.js"></script>
}
<h1>
@@ -29,25 +29,12 @@
<a href="https://www.nexusmods.com/stardewvalley/mods/2400"><img src="Content/images/nexus-icon.png" /> Download from Nexus</a>
<a href="@Model.StableVersion.DownloadUrl"><img src="Content/images/direct-download-icon.png" /> Direct download</a>
</div>
- </div><br />
-
- @if (Model.BetaVersion != null)
+ </div>
+ <div><a href="https://stardewvalleywiki.com/Modding:Player_Guide" class="secondary-cta">Player guide</a></div>
+ @if (Model.OtherBlurb != null)
{
- <div class="cta-dropdown secondary-cta-dropdown">
- <a href="@Model.BetaVersion.DownloadUrl" class="secondary-cta download">
- Download SMAPI @Model.BetaVersion.Version
- @if (!string.IsNullOrWhiteSpace(Model.BetaBlurb))
- {
- <br /><small>@Model.BetaBlurb</small>
- }
- </a><br />
- <div class="dropdown-content">
- <a href="https://www.nexusmods.com/stardewvalley/mods/2400"><img src="Content/images/nexus-icon.png" /> Download from Nexus</a>
- <a href="@Model.BetaVersion.DownloadUrl"><img src="Content/images/direct-download-icon.png" /> Direct download</a>
- </div>
- </div><br />
+ <div>@Html.Raw(Markdig.Markdown.ToHtml(Model.OtherBlurb))</div>
}
- <div><a href="https://stardewvalleywiki.com/Modding:Player_Guide" class="secondary-cta">Player guide</a></div>
</div>
<div class="area">
@@ -61,29 +48,11 @@
</div>
<div class="area">
- @if (Model.BetaVersion == null)
- {
- <h2 id="whatsnew">What's new</h2>
- <div class="github-description">
- @Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
- </div>
- <p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.PlainAction("Index", "Mods")">mod compatibility list</a> for more info.</p>
- }
- else
- {
- <h2 id="whatsnew">What's new in...</h2>
- <h3>SMAPI @Model.StableVersion.Version?</h3>
- <div class="github-description">
- @Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
- </div>
- <p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.PlainAction("Index", "Mods")">mod compatibility list</a> for more info.</p>
-
- <h3>SMAPI @Model.BetaVersion.Version?</h3>
- <div class="github-description">
- @Html.Raw(Markdig.Markdown.ToHtml(Model.BetaVersion.Description))
- </div>
- <p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.PlainAction("Index", "Mods")">mod compatibility list</a> for more info.</p>
- }
+ <h2 id="whatsnew">What's new</h2>
+ <div class="github-description">
+ @Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
+ </div>
+ <p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.PlainAction("Index", "Mods")">mod compatibility list</a> for more info.</p>
</div>
<div class="area">
@@ -122,10 +91,6 @@
<h2 id="modcreators">For mod creators</h2>
<ul>
<li><a href="@Model.StableVersion.DevDownloadUrl">SMAPI @Model.StableVersion.Version for developers</a> (includes <a href="https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense">intellisense</a> and full console output)</li>
- @if (Model.BetaVersion != null)
- {
- <li><a href="@Model.BetaVersion.DevDownloadUrl">SMAPI @Model.BetaVersion.Version for developers</a> (includes <a href="https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense">intellisense</a> and full console output)</li>
- }
<li><a href="https://stardewvalleywiki.com/Modding:Index">Modding documentation</a></li>
<li><a href="https://github.com/Pathoschild/SMAPI">Source code</a></li>
</ul>
diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json
index 22fd7396..0265a928 100644
--- a/src/SMAPI.Web/appsettings.json
+++ b/src/SMAPI.Web/appsettings.json
@@ -17,8 +17,7 @@
"Site": {
"BetaEnabled": false,
- "BetaBlurb": null,
- "SupporterList": null
+ "OtherBlurb": null
},
"ApiClients": {
@@ -76,6 +75,11 @@
"ID": "MartyrPher.SMAPI-Android-Installer",
"AllowNonStandardVersions": true
}
- ]
+ ],
+ "SmapiInfo": {
+ "ID": "Pathoschild.SMAPI",
+ "DefaultUpdateKey": "GitHub:Pathoschild/SMAPI",
+ "AddBetaUpdateKeys": [ "Nexus:2400" ]
+ }
}
}
diff --git a/src/SMAPI.Web/wwwroot/Content/css/index.css b/src/SMAPI.Web/wwwroot/Content/css/index.css
index 1cf8d261..150ccc0a 100644
--- a/src/SMAPI.Web/wwwroot/Content/css/index.css
+++ b/src/SMAPI.Web/wwwroot/Content/css/index.css
@@ -97,6 +97,10 @@ h1 {
display: block;
}
+.cta-blurb {
+ font-size: 0.85em;
+}
+
.sublinks {
font-size: 0.9em;
margin-bottom: 1em;
diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
index dcdd6298..bb166017 100644
--- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
+++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
@@ -168,6 +168,60 @@
},
/*********
+ ** Broke in SDV 1.5.5
+ *********/
+ "Animated Portrait Framework": {
+ "ID": "akai.AnimatedPortrait",
+ "~1.0.0 | Status": "AssumeBroken",
+ "~1.0.0 | StatusReasonDetails": "requires the 'System.Windows.Forms' API, which isn't available in .NET 5"
+ },
+ "Audio Devices": {
+ "ID": "maxvollmer.audiodevices",
+ "~3.0.1 | Status": "AssumeBroken",
+ "~3.0.1 | StatusReasonDetails": "fails to load due to an outdated implementation of the game's 'IAudioEngine' interface"
+ },
+ "Battery Warning": {
+ "ID": "Husky110.BatteryWarningMod",
+ "~1.0.4 | Status": "AssumeBroken",
+ "~1.0.4 | StatusReasonDetails": "requires the 'System.Management' API, which is a different DLL in .NET 5"
+ },
+ "Junimo Studio": {
+ "ID": "Becks723.JunimoStudio",
+ "~2.0.1 | Status": "AssumeBroken",
+ "~2.0.1 | StatusReasonDetails": "requires 'Microsoft.Xna.Framework.Audio.AudioCategory' which doesn't exist in MonoGame"
+ },
+ "Skip Intro": {
+ "ID": "Pathoschild.SkipIntro",
+ "~1.9.1 | Status": "AssumeBroken",
+ "~1.9.1 | StatusReasonDetails": "causes freeze during game launch"
+ },
+ "Stardew Hack": {
+ "ID": "bcmpinc.StardewHack",
+ "~5.1.0 | Status": "AssumeBroken",
+ "~5.1.0 | StatusReasonDetails": "runtime error when initializing due to an API change between .NET Framework and .NET 5"
+ },
+ "Stardew Valley Expanded": {
+ "ID": "FlashShifter.SVECode",
+ "~1.13.11 | Status": "AssumeBroken",
+ "~1.13.11 | StatusReasonDetails": "fails to load due to an outdated implementation of the game's 'ICue' interface"
+ },
+ "Stardew Web": {
+ "ID": "prism99.stardewweb",
+ "~0.7.2 | Status": "AssumeBroken",
+ "~0.7.2 | StatusReasonDetails": "requires the 'System.Drawing' API, which isn't available in .NET 5"
+ },
+ "Sundrop City": {
+ "ID": "SundropTeam.SundropCity",
+ "~0.4.1 | Status": "AssumeBroken",
+ "~0.4.1 | StatusReasonDetails": "causes freeze during game launch"
+ },
+ "Video Player": {
+ "ID": "aedenthorn.VideoPlayer",
+ "~0.2.5 | Status": "AssumeBroken",
+ "~0.2.5 | StatusReasonDetails": "requires an XNA Framework API that's not available in MonoGame and causes a crash to desktop"
+ },
+
+ /*********
** Broke in SMAPI 3.12.0
*********/
"Always Scroll Map": {
@@ -205,11 +259,6 @@
"~1.9.3 | Status": "AssumeBroken",
"~1.9.3 | StatusReasonDetails": "fails to load with 'ReflectionTypeLoadException' error"
},
- "Stardew Hack": {
- "ID": "bcmpinc.StardewHack",
- "~5.0.0 | Status": "AssumeBroken",
- "~5.0.0 | StatusReasonDetails": "causes Harmony patching errors for other mods"
- },
"Tilled Soil Decay": {
"ID": "bcmpinc.TilledSoilDecay",
"~4.1.0 | Status": "AssumeBroken",
@@ -238,12 +287,6 @@
/*********
** Broke in SDV 1.5 (SMAPI mods)
*********/
- "Audio Devices": {
- "ID": "maxvollmer.audiodevices",
- "~2.0.0 | Status": "AssumeBroken",
- "~2.0.0 | StatusReasonDetails": "causes crash to desktop when starting the game"
- },
-
"ChestEx": {
"ID": "berkayylmao.ChestEx",
"~1.3.4 | Status": "AssumeBroken",
diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
index aac4ff0f..d39574ef 100644
--- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
+++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
@@ -12,11 +12,11 @@
"properties": {
"Format": {
"title": "Format version",
- "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.",
+ "description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.",
"type": "string",
- "const": "1.23.0",
+ "const": "1.24.0",
"@errorMessages": {
- "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.23.0'."
+ "const": "Incorrect value '@value'. You should always use the latest format version (currently 1.24.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
}
},
"ConfigSchema": {
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 42c3b21b..cf4bb677 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -40,21 +40,16 @@ namespace StardewModdingAPI
internal static GamePlatform Platform { get; } = (GamePlatform)Enum.Parse(typeof(GamePlatform), LowLevelEnvironmentUtility.DetectPlatform());
/// <summary>The game framework running the game.</summary>
- internal static GameFramework GameFramework { get; } =
-#if SMAPI_FOR_XNA
- GameFramework.Xna;
-#else
- GameFramework.MonoGame;
-#endif
+ internal static GameFramework GameFramework { get; } = GameFramework.MonoGame;
/// <summary>The game's assembly name.</summary>
- internal static string GameAssemblyName => EarlyConstants.Platform == GamePlatform.Windows ? "Stardew Valley" : "StardewValley";
+ internal static string GameAssemblyName { get; } = "Stardew Valley";
/// <summary>The <see cref="Context.ScreenId"/> value which should appear in the SMAPI log, if any.</summary>
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.12.8";
+ internal static string RawApiVersion = "3.13.0";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
@@ -70,10 +65,10 @@ namespace StardewModdingAPI
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion(EarlyConstants.RawApiVersion);
/// <summary>The minimum supported version of Stardew Valley.</summary>
- public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.4");
+ public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.5");
/// <summary>The maximum supported version of Stardew Valley.</summary>
- public static ISemanticVersion MaximumGameVersion { get; } = new GameVersion("1.5.4");
+ public static ISemanticVersion MaximumGameVersion { get; } = null;
/// <summary>The target game platform.</summary>
public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform;
@@ -240,18 +235,13 @@ namespace StardewModdingAPI
// The game assembly can have one of three names depending how the mod was compiled:
// - 'StardewValley': assembly name on Linux/macOS;
// - 'Stardew Valley': assembly name on Windows;
- // - 'Netcode': an assembly that's separate on Windows only.
- resolver.AddWithExplicitNames(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley"
-#if !SMAPI_FOR_WINDOWS
- , "Netcode"
-#endif
- );
+ // - 'Netcode': an assembly that was separate on Windows only before Stardew Valley 1.5.5.
+ resolver.AddWithExplicitNames(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley", "Netcode");
}
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
/// <param name="targetPlatform">The target game platform.</param>
- /// <param name="framework">The game framework running the game.</param>
- internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform, GameFramework framework)
+ internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform)
{
var removeAssemblyReferences = new List<string>();
var targetAssemblies = new List<Assembly>();
@@ -260,61 +250,26 @@ namespace StardewModdingAPI
removeAssemblyReferences.Add("StardewModdingAPI.Toolkit.CoreInterfaces");
targetAssemblies.Add(typeof(StardewModdingAPI.IManifest).Assembly);
- // get changes for platform
- if (Constants.Platform != Platform.Windows)
- {
- removeAssemblyReferences.AddRange(new[]
- {
- "Netcode",
- "Stardew Valley"
- });
- targetAssemblies.Add(
- typeof(StardewValley.Game1).Assembly // note: includes Netcode types on Linux/macOS
- );
- }
- else
+ // XNA Framework before Stardew Valley 1.5.5
+ removeAssemblyReferences.AddRange(new[]
{
- removeAssemblyReferences.Add(
- "StardewValley"
- );
- targetAssemblies.AddRange(new[]
- {
- typeof(Netcode.NetBool).Assembly,
- typeof(StardewValley.Game1).Assembly
- });
- }
+ "Microsoft.Xna.Framework",
+ "Microsoft.Xna.Framework.Game",
+ "Microsoft.Xna.Framework.Graphics",
+ "Microsoft.Xna.Framework.Xact"
+ });
+ targetAssemblies.Add(
+ typeof(Microsoft.Xna.Framework.Vector2).Assembly
+ );
- // get changes for game framework
- switch (framework)
- {
- case GameFramework.MonoGame:
- removeAssemblyReferences.AddRange(new[]
- {
- "Microsoft.Xna.Framework",
- "Microsoft.Xna.Framework.Game",
- "Microsoft.Xna.Framework.Graphics",
- "Microsoft.Xna.Framework.Xact"
- });
- targetAssemblies.Add(
- typeof(Microsoft.Xna.Framework.Vector2).Assembly
- );
- break;
-
- case GameFramework.Xna:
- removeAssemblyReferences.Add(
- "MonoGame.Framework"
- );
- targetAssemblies.AddRange(new[]
- {
- typeof(Microsoft.Xna.Framework.Vector2).Assembly,
- typeof(Microsoft.Xna.Framework.Game).Assembly,
- typeof(Microsoft.Xna.Framework.Graphics.SpriteBatch).Assembly
- });
- break;
+ // `Netcode.dll` merged into the game assembly in Stardew Valley 1.5.5
+ removeAssemblyReferences.Add(
+ "Netcode"
+ );
- default:
- throw new InvalidOperationException($"Unknown game framework '{framework}'.");
- }
+ // Stardew Valley reference
+ removeAssemblyReferences.Add("StardewValley");
+ targetAssemblies.Add(typeof(StardewValley.Game1).Assembly);
return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences.ToArray(), targetAssemblies.ToArray());
}
diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs
index 4f810948..0a5fa7e7 100644
--- a/src/SMAPI/Framework/Content/AssetDataForMap.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using StardewModdingAPI.Toolkit.Utilities;
+using StardewValley;
using xTile;
using xTile.Layers;
using xTile.Tiles;
@@ -25,18 +26,17 @@ namespace StardewModdingAPI.Framework.Content
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
/// <inheritdoc />
- /// <remarks>Derived from <see cref="StardewValley.GameLocation.ApplyMapOverride"/> with a few changes:
+ /// <remarks>Derived from <see cref="GameLocation.ApplyMapOverride(Map,string,Rectangle?,Rectangle?)"/> with a few changes:
/// - can be applied directly to the maps when loading, before the location is created;
- /// - added support for source/target areas;
+ /// - added support for patch modes (overlay, replace by layer, or fully replace);
/// - added disambiguation if source has a modified version of the same tilesheet, instead of copying tiles into the target tilesheet;
- /// - changed to always overwrite tiles within the target area (to avoid edge cases where some tiles are only partly applied);
/// - fixed copying tilesheets (avoid "The specified TileSheet was not created for use with this map" error);
/// - fixed tilesheets not added at the end (via z_ prefix), which can cause crashes in game code which depends on hardcoded tilesheet indexes;
/// - fixed issue where different tilesheets are linked by ID.
/// </remarks>
- public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null)
+ public void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay)
{
- var target = this.Data;
+ Map target = this.Data;
// get areas
{
@@ -84,10 +84,13 @@ namespace StardewModdingAPI.Framework.Content
tilesheetMap[sourceSheet] = targetSheet;
}
- // get layer map
- IDictionary<Layer, Layer> layerMap = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id));
+ // get target layers
+ IDictionary<Layer, Layer> sourceToTargetLayers = source.Layers.ToDictionary(p => p, p => target.GetLayer(p.Id));
+ HashSet<Layer> orphanedTargetLayers = new HashSet<Layer>(target.Layers.Except(sourceToTargetLayers.Values));
// apply tiles
+ bool replaceAll = patchMode == PatchMapMode.Replace;
+ bool replaceByLayer = patchMode == PatchMapMode.ReplaceByLayer;
for (int x = 0; x < sourceArea.Value.Width; x++)
{
for (int y = 0; y < sourceArea.Value.Height; y++)
@@ -96,47 +99,37 @@ namespace StardewModdingAPI.Framework.Content
Point sourcePos = new Point(sourceArea.Value.X + x, sourceArea.Value.Y + y);
Point targetPos = new Point(targetArea.Value.X + x, targetArea.Value.Y + y);
+ // replace tiles on target-only layers
+ if (replaceAll)
+ {
+ foreach (Layer targetLayer in orphanedTargetLayers)
+ targetLayer.Tiles[targetPos.X, targetPos.Y] = null;
+ }
+
// merge layers
foreach (Layer sourceLayer in source.Layers)
{
// get layer
- Layer targetLayer = layerMap[sourceLayer];
+ Layer targetLayer = sourceToTargetLayers[sourceLayer];
if (targetLayer == null)
{
target.AddLayer(targetLayer = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize));
- layerMap[sourceLayer] = target.GetLayer(sourceLayer.Id);
+ sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id);
}
// copy layer properties
targetLayer.Properties.CopyFrom(sourceLayer.Properties);
- // copy tiles
+ // create new tile
Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y];
- Tile targetTile;
- switch (sourceTile)
- {
- case StaticTile _:
- targetTile = new StaticTile(targetLayer, tilesheetMap[sourceTile.TileSheet], sourceTile.BlendMode, sourceTile.TileIndex);
- break;
-
- case AnimatedTile animatedTile:
- {
- StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length];
- for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame)
- {
- StaticTile frameTile = animatedTile.TileFrames[frame];
- tileFrames[frame] = new StaticTile(targetLayer, tilesheetMap[frameTile.TileSheet], frameTile.BlendMode, frameTile.TileIndex);
- }
- targetTile = new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval);
- }
- break;
-
- default: // null or unhandled type
- targetTile = null;
- break;
- }
- targetTile?.Properties.CopyFrom(sourceTile.Properties);
- targetLayer.Tiles[targetPos.X, targetPos.Y] = targetTile;
+ Tile newTile = sourceTile != null
+ ? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet])
+ : null;
+ newTile?.Properties.CopyFrom(sourceTile.Properties);
+
+ // replace tile
+ if (newTile != null || replaceByLayer || replaceAll)
+ targetLayer.Tiles[targetPos.X, targetPos.Y] = newTile;
}
}
}
@@ -146,6 +139,33 @@ namespace StardewModdingAPI.Framework.Content
/*********
** Private methods
*********/
+ /// <summary>Create a new tile for the target map.</summary>
+ /// <param name="sourceTile">The source tile to copy.</param>
+ /// <param name="targetLayer">The target layer.</param>
+ /// <param name="targetSheet">The target tilesheet.</param>
+ private Tile CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet)
+ {
+ switch (sourceTile)
+ {
+ case StaticTile _:
+ return new StaticTile(targetLayer, targetSheet, sourceTile.BlendMode, sourceTile.TileIndex);
+
+ case AnimatedTile animatedTile:
+ {
+ StaticTile[] tileFrames = new StaticTile[animatedTile.TileFrames.Length];
+ for (int frame = 0; frame < animatedTile.TileFrames.Length; ++frame)
+ {
+ StaticTile frameTile = animatedTile.TileFrames[frame];
+ tileFrames[frame] = new StaticTile(targetLayer, targetSheet, frameTile.BlendMode, frameTile.TileIndex);
+ }
+
+ return new AnimatedTile(targetLayer, tileFrames, animatedTile.FrameInterval);
+ }
+
+ default: // null or unhandled type
+ return null;
+ }
+ }
/// <summary>Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path.</summary>
/// <param name="path">The path to normalize.</param>
private string NormalizeTilesheetPathForComparison(string path)
diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs
index 7edc9ab9..8e0c6228 100644
--- a/src/SMAPI/Framework/Content/ContentCache.cs
+++ b/src/SMAPI/Framework/Content/ContentCache.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
-using Microsoft.Xna.Framework;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
@@ -18,9 +17,6 @@ namespace StardewModdingAPI.Framework.Content
/// <summary>The underlying asset cache.</summary>
private readonly IDictionary<string, object> Cache;
- /// <summary>Applies platform-specific asset key normalization so it's consistent with the underlying cache.</summary>
- private readonly Func<string, string> NormalizeAssetNameForPlatform;
-
/*********
** Accessors
@@ -48,17 +44,7 @@ namespace StardewModdingAPI.Framework.Content
/// <param name="reflection">Simplifies access to private game code.</param>
public ContentCache(LocalizedContentManager contentManager, Reflector reflection)
{
- // init
this.Cache = reflection.GetField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue();
-
- // get key normalization logic
- if (Constants.GameFramework == GameFramework.Xna)
- {
- IReflectedMethod method = reflection.GetMethod(typeof(TitleContainer), "GetCleanPath");
- this.NormalizeAssetNameForPlatform = path => method.Invoke<string>(path);
- }
- else
- this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load<T> logic
}
/****
@@ -75,23 +61,24 @@ namespace StardewModdingAPI.Framework.Content
/****
** Normalize
****/
- /// <summary>Normalize path separators in a file path. For asset keys, see <see cref="NormalizeKey"/> instead.</summary>
+ /// <summary>Normalize path separators in an asset name.</summary>
/// <param name="path">The file path to normalize.</param>
[Pure]
public string NormalizePathSeparators(string path)
{
- return PathUtilities.NormalizePath(path);
+ return PathUtilities.NormalizeAssetName(path);
}
/// <summary>Normalize a cache key so it's consistent with the underlying cache.</summary>
/// <param name="key">The asset key.</param>
+ /// <remarks>This is equivalent to <see cref="NormalizePathSeparators"/> with added file extension logic.</remarks>
[Pure]
public string NormalizeKey(string key)
{
key = this.NormalizePathSeparators(key);
return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase)
? key.Substring(0, key.Length - 4)
- : this.NormalizeAssetNameForPlatform(key);
+ : key;
}
/****
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index d0e759c2..b6f1669a 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -64,6 +64,9 @@ namespace StardewModdingAPI.Framework
/// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary>
private readonly LocalizedContentManager VanillaContentManager;
+ /// <summary>The language enum values indexed by locale code.</summary>
+ private Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>> LocaleCodes;
+
/*********
** Accessors
@@ -133,6 +136,7 @@ namespace StardewModdingAPI.Framework
this.ContentManagers.Add(contentManagerForAssetPropagation);
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations);
+ this.LocaleCodes = new Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>>(this.GetLocaleCodes);
}
/// <summary>Get a new content manager which handles reading files from the game content folder with support for interception.</summary>
@@ -195,6 +199,10 @@ namespace StardewModdingAPI.Framework
/// <summary>Perform any cleanup needed when the locale changes.</summary>
public void OnLocaleChanged()
{
+ // rebuild locale cache (which may change due to custom mod languages)
+ this.LocaleCodes = new Lazy<IDictionary<string, LocalizedContentManager.LanguageCode>>(this.GetLocaleCodes);
+
+ // reload affected content
this.ContentManagerLock.InReadLock(() =>
{
foreach (IContentManager contentManager in this.ContentManagers)
@@ -408,6 +416,25 @@ namespace StardewModdingAPI.Framework
return tilesheets ?? new TilesheetReference[0];
}
+ /// <summary>Get the language enum which corresponds to a locale code (e.g. <see cref="LocalizedContentManager.LanguageCode.fr"/> given <c>fr-FR</c>).</summary>
+ /// <param name="locale">The locale code to search. This must exactly match the language; no fallback is performed.</param>
+ /// <param name="language">The matched language enum, if any.</param>
+ /// <returns>Returns whether a valid language was found.</returns>
+ public bool TryGetLanguageEnum(string locale, out LocalizedContentManager.LanguageCode language)
+ {
+ return this.LocaleCodes.Value.TryGetValue(locale, out language);
+ }
+
+ /// <summary>Get the locale code which corresponds to a language enum (e.g. <c>fr-FR</c> given <see cref="LocalizedContentManager.LanguageCode.fr"/>).</summary>
+ /// <param name="language">The language enum to search.</param>
+ public string GetLocaleCode(LocalizedContentManager.LanguageCode language)
+ {
+ if (language == LocalizedContentManager.LanguageCode.mod && LocalizedContentManager.CurrentModLanguage == null)
+ return null;
+
+ return Game1.content.LanguageCodeString(language);
+ }
+
/// <summary>Dispose held resources.</summary>
public void Dispose()
{
@@ -457,5 +484,19 @@ namespace StardewModdingAPI.Framework
return false;
}
}
+
+ /// <summary>Get the language enums (like <see cref="LocalizedContentManager.LanguageCode.ja"/>) indexed by locale code (like <c>ja-JP</c>).</summary>
+ private IDictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes()
+ {
+ IDictionary<string, LocalizedContentManager.LanguageCode> map = new Dictionary<string, LocalizedContentManager.LanguageCode>();
+ foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode)))
+ {
+ string locale = this.GetLocaleCode(code);
+ if (locale != null)
+ map[locale] = code;
+ }
+
+ return map;
+ }
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 7244a534..5645c0fa 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -38,9 +38,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>A callback to invoke when the content manager is being disposed.</summary>
private readonly Action<BaseContentManager> OnDisposing;
- /// <summary>The language enum values indexed by locale code.</summary>
- protected IDictionary<string, LanguageCode> LanguageCodes { get; }
-
/// <summary>A list of disposable assets.</summary>
private readonly List<WeakReference<IDisposable>> Disposables = new List<WeakReference<IDisposable>>();
@@ -92,7 +89,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
// get asset data
- this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.OrdinalIgnoreCase);
this.BaseDisposableReferences = reflection.GetField<List<IDisposable>>(this, "disposableAssets").GetValue();
}
@@ -292,7 +288,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (lastSepIndex >= 0)
{
string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
- if (this.LanguageCodes.ContainsKey(suffix))
+ if (this.Coordinator.TryGetLanguageEnum(suffix, out _))
{
assetName = cacheKey.Substring(0, lastSepIndex);
localeCode = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
@@ -311,17 +307,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="language">The language to check.</param>
protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language);
- /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
- private IDictionary<LanguageCode, string> GetKeyLocales()
- {
- // create locale => code map
- IDictionary<LanguageCode, string> map = new Dictionary<LanguageCode, string>();
- foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
- map[code] = this.GetLocale(code);
-
- return map;
- }
-
/// <summary>Get the asset name from a cache key.</summary>
/// <param name="cacheKey">The input cache key.</param>
private string GetAssetName(string cacheKey)
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 38bcf153..7a49dd36 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -249,7 +249,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// extract language code
int splitIndex = rawAsset.LastIndexOf('.');
- if (splitIndex != -1 && this.LanguageCodes.TryGetValue(rawAsset.Substring(splitIndex + 1), out language))
+ if (splitIndex != -1 && this.Coordinator.TryGetLanguageEnum(rawAsset.Substring(splitIndex + 1), out language))
{
assetName = rawAsset.Substring(0, splitIndex);
return true;
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index d24ffb81..beb90a5d 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -2,12 +2,12 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
+using BmFont;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
-using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
@@ -130,6 +130,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
break;
+ // unpacked Bitmap font
+ case ".fnt":
+ {
+ string source = File.ReadAllText(file.FullName);
+ asset = (T)(object)new XmlSource(source);
+ }
+ break;
+
// unpacked data
case ".json":
{
@@ -172,13 +180,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
break;
default:
- throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.json', '.png', '.tbin', or '.xnb'.");
+ throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', or '.xnb'.");
}
}
catch (Exception ex) when (!(ex is SContentLoadException))
{
- if (ex.GetInnermostException() is DllNotFoundException dllEx && dllEx.Message == "libgdiplus.dylib")
- throw GetContentError("couldn't find libgdiplus, which is needed to load mod images. Make sure Mono is installed and you're running the game through the normal launcher.");
throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex);
}
diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
index f5f2d916..b0bb7f80 100644
--- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
@@ -157,11 +158,8 @@ namespace StardewModdingAPI.Framework.Input
yield break;
// buttons
- foreach (var pair in this.ButtonStates)
- {
- if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
- yield return button.ToSButton();
- }
+ foreach (Buttons button in this.GetPressedGamePadButtons())
+ yield return button.ToSButton();
// triggers
if (this.LeftTrigger > 0.2f)
@@ -201,7 +199,7 @@ namespace StardewModdingAPI.Framework.Input
rightThumbStick: this.RightStickPos,
leftTrigger: this.LeftTrigger,
rightTrigger: this.RightTrigger,
- buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values
+ buttons: this.GetPressedGamePadButtons().ToArray()
);
return this.State.Value;
@@ -211,17 +209,14 @@ namespace StardewModdingAPI.Framework.Input
/*********
** Private methods
*********/
- /// <summary>Get a bitmask representing the pressed buttons.</summary>
- private Buttons GetButtonBitmask()
+ /// <summary>Get the pressed gamepad buttons.</summary>
+ private IEnumerable<Buttons> GetPressedGamePadButtons()
{
- Buttons flag = 0;
foreach (var pair in this.ButtonStates)
{
if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
- flag |= button;
+ yield return button;
}
-
- return flag;
}
}
}
diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs
index 6c9a5f3b..4cb77a45 100644
--- a/src/SMAPI/Framework/InternalExtensions.cs
+++ b/src/SMAPI/Framework/InternalExtensions.cs
@@ -6,7 +6,6 @@ using System.Threading;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Reflection;
-using StardewValley;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework
@@ -150,11 +149,7 @@ namespace StardewModdingAPI.Framework
/// <param name="reflection">The reflection helper with which to access private fields.</param>
public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection)
{
- string fieldName = Constants.GameFramework == GameFramework.Xna
- ? "inBeginEndPair"
- : "_beginCalled";
-
- return reflection.GetField<bool>(Game1.spriteBatch, fieldName).GetValue();
+ return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
}
}
}
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index f2876146..5a291d0a 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -250,36 +250,7 @@ namespace StardewModdingAPI.Framework.Logging
/// <param name="exception">The exception details.</param>
public void LogFatalLaunchError(Exception exception)
{
- switch (exception)
- {
- // audio crash
- case InvalidOperationException ex when ex.Source == "Microsoft.Xna.Framework.Xact" && ex.StackTrace.Contains("Microsoft.Xna.Framework.Audio.AudioEngine..ctor"):
- this.Monitor.Log("The game couldn't load audio. Do you have speakers or headphones plugged in?", LogLevel.Error);
- this.Monitor.Log($"Technical details: {ex.GetLogSummary()}");
- break;
-
- // missing content folder exception
- case FileNotFoundException ex when ex.Message == "Couldn't find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.": // path in error is hardcoded regardless of install path
- this.Monitor.Log("The game can't find its Content\\XACT\\FarmerSounds.xgs file. You can usually fix this by resetting your content files (see https://smapi.io/troubleshoot#reset-content ), or by uninstalling and reinstalling the game.", LogLevel.Error);
- this.Monitor.Log($"Technical details: {ex.GetLogSummary()}");
- break;
-
- // path too long exception
- case PathTooLongException _:
- {
- string[] affectedPaths = PathUtilities.GetTooLongPaths(Constants.ModsPath).ToArray();
- string message = affectedPaths.Any()
- ? $"SMAPI can't launch because some of your mod files exceed the maximum path length on {Constants.Platform}.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", affectedPaths)}"
- : $"The game failed to launch: {exception.GetLogSummary()}";
- this.MonitorForGame.Log(message, LogLevel.Error);
- }
- break;
-
- // generic exception
- default:
- this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error);
- break;
- }
+ this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error);
}
/****
@@ -290,7 +261,7 @@ namespace StardewModdingAPI.Framework.Logging
/// <param name="customSettings">The custom SMAPI settings.</param>
public void LogIntro(string modsPath, IDictionary<string, object> customSettings)
{
- // log platform & patches
+ // log platform
this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
// log basic info
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 57a76a35..cb5fa2ae 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -53,16 +53,15 @@ namespace StardewModdingAPI.Framework.ModLoading
*********/
/// <summary>Construct an instance.</summary>
/// <param name="targetPlatform">The current game platform.</param>
- /// <param name="framework">The game framework running the game.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="paranoidMode">Whether to detect paranoid mode issues.</param>
/// <param name="rewriteMods">Whether to rewrite mods for compatibility.</param>
- public AssemblyLoader(Platform targetPlatform, GameFramework framework, IMonitor monitor, bool paranoidMode, bool rewriteMods)
+ public AssemblyLoader(Platform targetPlatform, IMonitor monitor, bool paranoidMode, bool rewriteMods)
{
this.Monitor = monitor;
this.ParanoidMode = paranoidMode;
this.RewriteMods = rewriteMods;
- this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform, framework));
+ this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform));
// init resolver
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs
index aefd1c20..a064f503 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs
@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
{
- /// <summary>Provides <see cref="SpriteBatch"/> method signatures that can be injected into mod code for compatibility between Linux/macOS or Windows.</summary>
+ /// <summary>Provides <see cref="SpriteBatch"/> method signatures that can be injected into mod code for compatibility with mods written for XNA Framework before Stardew Valley 1.5.5.</summary>
/// <remarks>This is public to support SMAPI rewriting and should not be referenced directly by mods.</remarks>
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")]
[SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/macOS.")]
@@ -19,14 +19,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
/****
- ** MonoGame signatures
- ****/
- public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix)
- {
- base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity);
- }
-
- /****
** XNA signatures
****/
public new void Begin()
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
index 8d1b6034..5acba569 100644
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
+++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
@@ -24,7 +24,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <summary>Construct an instance.</summary>
public InterfaceProxyFactory()
{
- AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
+ AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 6dffb1de..55a7f083 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -11,14 +11,10 @@ using System.Runtime.ExceptionServices;
using System.Security;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Xna.Framework;
#if SMAPI_FOR_WINDOWS
using Microsoft.Win32;
#endif
-#if SMAPI_FOR_XNA
-using System.Windows.Forms;
-#endif
using Newtonsoft.Json;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Events;
@@ -224,10 +220,6 @@ namespace StardewModdingAPI.Framework
this.Toolkit.JsonHelper.JsonSettings.Converters.Add(converter);
// add error handlers
-#if SMAPI_FOR_XNA
- Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error);
- Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
-#endif
AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error);
// add more lenient assembly resolver
@@ -243,7 +235,7 @@ namespace StardewModdingAPI.Framework
monitor: this.Monitor,
reflection: this.Reflection,
eventManager: this.EventManager,
- modHooks: new SModHooks(this.OnNewDayAfterFade),
+ modHooks: new SModHooks(this.OnNewDayAfterFade, this.Monitor),
multiplayer: this.Multiplayer,
exitGameImmediately: this.ExitGameImmediately,
@@ -657,13 +649,6 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log("Game loader done.");
}
- if (instance.NewDayTask?.Status == TaskStatus.Created)
- {
- this.Monitor.Log("New day task synchronizing...");
- instance.NewDayTask.RunSynchronously();
- this.Monitor.Log("New day task done.");
- }
-
// While a background task is in progress, the game may make changes to the game
// state while mods are running their code. This is risky, because data changes can
// conflict (e.g. collection changed during enumeration errors) and data may change
@@ -673,7 +658,7 @@ namespace StardewModdingAPI.Framework
// a small chance that the task will finish after we defer but before the game checks,
// which means technically events should be raised, but the effects of missing one
// update tick are negligible and not worth the complications of bypassing Game1.Update.
- if (instance.NewDayTask != null || Game1.gameMode == Game1.loadingMode)
+ if (Game1.gameMode == Game1.loadingMode)
{
events.UnvalidatedUpdateTicking.RaiseEmpty();
runUpdate();
@@ -766,7 +751,7 @@ namespace StardewModdingAPI.Framework
** Locale changed events
*********/
if (state.Locale.IsChanged)
- this.Monitor.Log($"Context: locale set to {state.Locale.New}.");
+ this.Monitor.Log($"Context: locale set to {state.Locale.New} ({this.ContentCore.GetLocaleCode(state.Locale.New)}).");
/*********
** Load / return-to-title events
@@ -776,7 +761,7 @@ namespace StardewModdingAPI.Framework
else if (Context.IsWorldReady && Context.LoadStage != LoadStage.Ready)
{
// print context
- string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.Language}.";
+ string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.GetLocale()}.";
if (Context.IsMultiplayer)
{
int onlineCount = Game1.getOnlineFarmers().Count();
@@ -1304,9 +1289,6 @@ namespace StardewModdingAPI.Framework
{
// create client
string url = this.Settings.WebApiBaseUrl;
-#if !SMAPI_FOR_WINDOWS
- url = url.Replace("https://", "http://"); // workaround for OpenSSL issues with the game's bundled Mono on Linux/macOS
-#endif
WebApiClient client = new WebApiClient(url, Constants.ApiVersion);
this.Monitor.Log("Checking for updates...");
@@ -1491,7 +1473,7 @@ namespace StardewModdingAPI.Framework
// load mods
IList<IModMetadata> skippedMods = new List<IModMetadata>();
- using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, Constants.GameFramework, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods))
+ using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods))
{
// init
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
@@ -1701,9 +1683,8 @@ namespace StardewModdingAPI.Framework
catch (Exception ex)
{
errorReasonPhrase = "its DLL couldn't be loaded.";
- // re-enable in Stardew Valley 1.5.5
- //if (ex is BadImageFormatException && !EnvironmentUtility.Is64BitAssembly(assemblyPath))
- // errorReasonPhrase = "it needs to be updated for 64-bit mode.";
+ if (ex is BadImageFormatException && !EnvironmentUtility.Is64BitAssembly(assemblyPath))
+ errorReasonPhrase = "it needs to be updated for 64-bit mode.";
errorDetails = $"Error: {ex.GetLogSummary()}";
failReason = ModFailReason.LoadFailed;
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 4e134455..898ed1f5 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -252,7 +252,7 @@ namespace StardewModdingAPI.Framework
/// <summary>Replicate the game's draw logic with some changes for SMAPI.</summary>
/// <param name="gameTime">A snapshot of the game timing state.</param>
/// <param name="target_screen">The render target, if any.</param>
- /// <remarks>This implementation is identical to <see cref="Game1.Draw"/>, except for try..catch around menu draw code, private field references replaced by wrappers, and added events.</remarks>
+ /// <remarks>This implementation is identical to <see cref="Game1._draw"/>, except for try..catch around menu draw code, private field references replaced by wrappers, and added events.</remarks>
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")]
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "copied from game code as-is")]
[SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")]
@@ -286,7 +286,7 @@ namespace StardewModdingAPI.Framework
IClickableMenu menu = Game1.activeClickableMenu;
if (menu != null)
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.Rendering.RaiseEmpty();
try
{
@@ -304,7 +304,7 @@ namespace StardewModdingAPI.Framework
}
if (Game1.overlayMenu != null)
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
Game1.overlayMenu.draw(Game1.spriteBatch);
Game1.spriteBatch.End();
}
@@ -315,7 +315,7 @@ namespace StardewModdingAPI.Framework
if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet() && !this.takingMapScreenshot)
{
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.Rendering.RaiseEmpty();
IClickableMenu curMenu = null;
@@ -346,11 +346,11 @@ namespace StardewModdingAPI.Framework
}
if (Game1.gameMode == 11)
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.Rendering.RaiseEmpty();
- Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Microsoft.Xna.Framework.Color.HotPink);
- Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Microsoft.Xna.Framework.Color(0, 255, 0));
- Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Microsoft.Xna.Framework.Color.White);
+ Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink);
+ Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, 255, 0));
+ Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
events.Rendered.RaiseEmpty();
Game1.spriteBatch.End();
return;
@@ -368,8 +368,8 @@ namespace StardewModdingAPI.Framework
if (Game1.globalFade && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause))
{
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
Game1.spriteBatch.End();
Game1.PopUIMode();
}
@@ -388,7 +388,7 @@ namespace StardewModdingAPI.Framework
if (Game1.showingEndOfNightStuff)
{
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.Rendering.RaiseEmpty();
if (Game1.activeClickableMenu != null)
{
@@ -417,16 +417,16 @@ namespace StardewModdingAPI.Framework
{
Game1.PushUIMode();
base.GraphicsDevice.Clear(Game1.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.Rendering.RaiseEmpty();
string addOn = "";
for (int i = 0; (double)i < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0; i++)
{
addOn += ".";
}
- string str = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688");
- string msg = str + addOn;
- string largestMessage = str + "... ";
+ string text = Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3688");
+ string msg = text + addOn;
+ string largestMessage = text + "... ";
int msgw = SpriteText.getWidthOfString(largestMessage);
int msgh = 64;
int msgx = 64;
@@ -442,7 +442,7 @@ namespace StardewModdingAPI.Framework
byte batchOpens = 0; // used for rendering event
if (Game1.gameMode == 0)
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if (++batchOpens == 1)
events.Rendering.RaiseEmpty();
}
@@ -456,7 +456,7 @@ namespace StardewModdingAPI.Framework
if (Game1.drawLighting)
{
Game1.SetRenderTarget(Game1.lightmap);
- base.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White * 0f);
+ base.GraphicsDevice.Clear(Color.White * 0f);
Matrix lighting_matrix = Matrix.Identity;
if (this.useUnscaledLighting)
{
@@ -465,13 +465,13 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, lighting_matrix);
if (++batchOpens == 1)
events.Rendering.RaiseEmpty();
- Microsoft.Xna.Framework.Color lighting = (Game1.currentLocation.Name.StartsWith("UndergroundMine") && Game1.currentLocation is MineShaft) ? (Game1.currentLocation as MineShaft).getLightingColor(gameTime) : ((Game1.ambientLight.Equals(Microsoft.Xna.Framework.Color.White) || (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors)) ? Game1.outdoorLight : Game1.ambientLight);
+ Color lighting = ((Game1.currentLocation.Name.StartsWith("UndergroundMine") && Game1.currentLocation is MineShaft) ? (Game1.currentLocation as MineShaft).getLightingColor(gameTime) : ((Game1.ambientLight.Equals(Color.White) || (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors)) ? Game1.outdoorLight : Game1.ambientLight));
float light_multiplier = 1f;
if (Game1.player.hasBuff(26))
{
- if (lighting == Microsoft.Xna.Framework.Color.White)
+ if (lighting == Color.White)
{
- lighting = new Microsoft.Xna.Framework.Color(0.75f, 0.75f, 0.75f);
+ lighting = new Color(0.75f, 0.75f, 0.75f);
}
else
{
@@ -504,12 +504,8 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.End();
Game1.SetRenderTarget(target_screen);
}
- if (Game1.bloomDay && Game1.bloom != null)
- {
- Game1.bloom.BeginDraw();
- }
base.GraphicsDevice.Clear(Game1.bgColor);
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if (++batchOpens == 1)
events.Rendering.RaiseEmpty();
events.RenderingWorld.RaiseEmpty();
@@ -522,10 +518,10 @@ namespace StardewModdingAPI.Framework
Game1.currentLocation.Map.GetLayer("Back").Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, wrapAround: false, 4);
Game1.currentLocation.drawWater(Game1.spriteBatch);
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
Game1.currentLocation.drawFloorDecorations(Game1.spriteBatch);
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
this._farmerShadows.Clear();
if (Game1.currentLocation.currentEvent != null && !Game1.currentLocation.currentEvent.isFestival && Game1.currentLocation.currentEvent.farmerActors.Count > 0)
{
@@ -555,17 +551,17 @@ namespace StardewModdingAPI.Framework
{
if (!k.swimming && !k.HideShadow && !k.IsInvisible && !this.checkCharacterTilesForShadowDrawFlag(k))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, k.GetShadowOffset() + k.Position + new Vector2((float)(k.GetSpriteWidthForPositioning() * 4) / 2f, k.GetBoundingBox().Height + ((!k.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)k.yJumpOffset / 40f) * (float)k.scale), SpriteEffects.None, Math.Max(0f, (float)k.getStandingY() / 10000f) - 1E-06f);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, k.GetShadowOffset() + k.Position + new Vector2((float)(k.GetSpriteWidthForPositioning() * 4) / 2f, k.GetBoundingBox().Height + ((!k.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)k.yJumpOffset / 40f) * (float)k.scale), SpriteEffects.None, Math.Max(0f, (float)k.getStandingY() / 10000f) - 1E-06f);
}
}
}
else
{
- foreach (NPC m in Game1.CurrentEvent.actors)
+ foreach (NPC l in Game1.CurrentEvent.actors)
{
- if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(m)) && !m.swimming && !m.HideShadow && !this.checkCharacterTilesForShadowDrawFlag(m))
+ if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(l)) && !l.swimming && !l.HideShadow && !this.checkCharacterTilesForShadowDrawFlag(l))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, m.GetShadowOffset() + m.Position + new Vector2((float)(m.GetSpriteWidthForPositioning() * 4) / 2f, m.GetBoundingBox().Height + ((!m.IsMonster) ? ((m.Sprite.SpriteHeight <= 16) ? (-4) : 12) : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, 4f + (float)m.yJumpOffset / 40f) * (float)m.scale, SpriteEffects.None, Math.Max(0f, (float)m.getStandingY() / 10000f) - 1E-06f);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, l.GetShadowOffset() + l.Position + new Vector2((float)(l.GetSpriteWidthForPositioning() * 4) / 2f, l.GetBoundingBox().Height + ((!l.IsMonster) ? ((l.Sprite.SpriteHeight <= 16) ? (-4) : 12) : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, 4f + (float)l.yJumpOffset / 40f) * (float)l.scale, SpriteEffects.None, Math.Max(0f, (float)l.getStandingY() / 10000f) - 1E-06f);
}
}
}
@@ -573,7 +569,7 @@ namespace StardewModdingAPI.Framework
{
if (!Game1.multiplayer.isDisconnecting(f3.UniqueMultiplayerID) && !f3.swimming && !f3.isRidingHorse() && !f3.IsSitting() && (Game1.currentLocation == null || !this.checkCharacterTilesForShadowDrawFlag(f3)))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f3.GetShadowOffset() + f3.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f3.running || f3.UsingTool) && f3.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f3.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, 0f);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f3.GetShadowOffset() + f3.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f3.running || f3.UsingTool) && f3.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f3.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, 0f);
}
}
}
@@ -581,26 +577,26 @@ namespace StardewModdingAPI.Framework
building_layer.Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, wrapAround: false, 4);
Game1.mapDisplayDevice.EndScene();
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
if (!Game1.currentLocation.shouldHideCharacters())
{
if (Game1.CurrentEvent == null)
{
- foreach (NPC n in Game1.currentLocation.characters)
+ foreach (NPC m in Game1.currentLocation.characters)
{
- if (!n.swimming && !n.HideShadow && !n.isInvisible && this.checkCharacterTilesForShadowDrawFlag(n))
+ if (!m.swimming && !m.HideShadow && !m.isInvisible && this.checkCharacterTilesForShadowDrawFlag(m))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n.GetShadowOffset() + n.Position + new Vector2((float)(n.GetSpriteWidthForPositioning() * 4) / 2f, n.GetBoundingBox().Height + ((!n.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n.yJumpOffset / 40f) * (float)n.scale), SpriteEffects.None, Math.Max(0f, (float)n.getStandingY() / 10000f) - 1E-06f);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, m.GetShadowOffset() + m.Position + new Vector2((float)(m.GetSpriteWidthForPositioning() * 4) / 2f, m.GetBoundingBox().Height + ((!m.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)m.yJumpOffset / 40f) * (float)m.scale), SpriteEffects.None, Math.Max(0f, (float)m.getStandingY() / 10000f) - 1E-06f);
}
}
}
else
{
- foreach (NPC n2 in Game1.CurrentEvent.actors)
+ foreach (NPC n in Game1.CurrentEvent.actors)
{
- if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(n2)) && !n2.swimming && !n2.HideShadow && this.checkCharacterTilesForShadowDrawFlag(n2))
+ if ((Game1.CurrentEvent == null || !Game1.CurrentEvent.ShouldHideCharacter(n)) && !n.swimming && !n.HideShadow && this.checkCharacterTilesForShadowDrawFlag(n))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n2.GetShadowOffset() + n2.Position + new Vector2((float)(n2.GetSpriteWidthForPositioning() * 4) / 2f, n2.GetBoundingBox().Height + ((!n2.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n2.yJumpOffset / 40f) * (float)n2.scale), SpriteEffects.None, Math.Max(0f, (float)n2.getStandingY() / 10000f) - 1E-06f);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, n.GetShadowOffset() + n.Position + new Vector2((float)(n.GetSpriteWidthForPositioning() * 4) / 2f, n.GetBoundingBox().Height + ((!n.IsMonster) ? 12 : 0))), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), Math.Max(0f, (4f + (float)n.yJumpOffset / 40f) * (float)n.scale), SpriteEffects.None, Math.Max(0f, (float)n.getStandingY() / 10000f) - 1E-06f);
}
}
}
@@ -609,7 +605,7 @@ namespace StardewModdingAPI.Framework
float draw_layer = Math.Max(0.0001f, f4.getDrawLayer() + 0.00011f) - 0.0001f;
if (!f4.swimming && !f4.isRidingHorse() && !f4.IsSitting() && Game1.currentLocation != null && this.checkCharacterTilesForShadowDrawFlag(f4))
{
- Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f4.GetShadowOffset() + f4.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Microsoft.Xna.Framework.Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f4.running || f4.UsingTool) && f4.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f4.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, draw_layer);
+ Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(f4.GetShadowOffset() + f4.Position + new Vector2(32f, 24f)), Game1.shadowTexture.Bounds, Color.White, 0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f - (((f4.running || f4.UsingTool) && f4.FarmerSprite.currentAnimationIndex > 1) ? ((float)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[f4.FarmerSprite.CurrentFrame]) * 0.5f) : 0f), SpriteEffects.None, draw_layer);
}
}
}
@@ -619,7 +615,7 @@ namespace StardewModdingAPI.Framework
}
if (Game1.player.currentUpgrade != null && Game1.player.currentUpgrade.daysLeftTillUpgradeDone <= 3 && Game1.currentLocation.Name.Equals("Farm"))
{
- Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), Game1.player.currentUpgrade.getSourceRectangle(), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, (Game1.player.currentUpgrade.positionOfCarpenter.Y + 48f) / 10000f);
+ Game1.spriteBatch.Draw(Game1.player.currentUpgrade.workerTexture, Game1.GlobalToLocal(Game1.viewport, Game1.player.currentUpgrade.positionOfCarpenter), Game1.player.currentUpgrade.getSourceRectangle(), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, (Game1.player.currentUpgrade.positionOfCarpenter.Y + 48f) / 10000f);
}
Game1.currentLocation.draw(Game1.spriteBatch);
foreach (Vector2 tile_position in Game1.crabPotOverlayTiles.Keys)
@@ -646,14 +642,14 @@ namespace StardewModdingAPI.Framework
}
if (Game1.tvStation >= 0)
{
- Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(400f, 160f)), new Microsoft.Xna.Framework.Rectangle(Game1.tvStation * 24, 0, 24, 15), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
+ Game1.spriteBatch.Draw(Game1.tvStationTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(400f, 160f)), new Microsoft.Xna.Framework.Rectangle(Game1.tvStation * 24, 0, 24, 15), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, 1E-08f);
}
if (Game1.panMode)
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((int)Math.Floor((double)(Game1.getOldMouseX() + Game1.viewport.X) / 64.0) * 64 - Game1.viewport.X, (int)Math.Floor((double)(Game1.getOldMouseY() + Game1.viewport.Y) / 64.0) * 64 - Game1.viewport.Y, 64, 64), Microsoft.Xna.Framework.Color.Lime * 0.75f);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((int)Math.Floor((double)(Game1.getOldMouseX() + Game1.viewport.X) / 64.0) * 64 - Game1.viewport.X, (int)Math.Floor((double)(Game1.getOldMouseY() + Game1.viewport.Y) / 64.0) * 64 - Game1.viewport.Y, 64, 64), Color.Lime * 0.75f);
foreach (Warp w in Game1.currentLocation.warps)
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(w.X * 64 - Game1.viewport.X, w.Y * 64 - Game1.viewport.Y, 64, 64), Microsoft.Xna.Framework.Color.Red * 0.75f);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(w.X * 64 - Game1.viewport.X, w.Y * 64 - Game1.viewport.Y, 64, 64), Color.Red * 0.75f);
}
}
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
@@ -661,7 +657,7 @@ namespace StardewModdingAPI.Framework
Game1.mapDisplayDevice.EndScene();
Game1.currentLocation.drawAboveFrontLayer(Game1.spriteBatch);
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
{
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
@@ -670,7 +666,7 @@ namespace StardewModdingAPI.Framework
}
if (Game1.toolHold > 400f && Game1.player.CurrentTool.UpgradeLevel >= 1 && Game1.player.canReleaseTool)
{
- Microsoft.Xna.Framework.Color barColor = Microsoft.Xna.Framework.Color.White;
+ Color barColor = Color.White;
switch ((int)(Game1.toolHold / 600f) + 2)
{
case 1:
@@ -686,7 +682,7 @@ namespace StardewModdingAPI.Framework
barColor = Tool.iridiumColor;
break;
}
- Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0) - 2, (int)(Game1.toolHold % 600f * 0.08f) + 4, 12), Microsoft.Xna.Framework.Color.Black);
+ Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X - 2, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0) - 2, (int)(Game1.toolHold % 600f * 0.08f) + 4, 12), Color.Black);
Game1.spriteBatch.Draw(Game1.littleEffect, new Microsoft.Xna.Framework.Rectangle((int)Game1.player.getLocalPosition(Game1.viewport).X, (int)Game1.player.getLocalPosition(Game1.viewport).Y - ((!Game1.player.CurrentTool.Name.Equals("Watering Can")) ? 64 : 0), (int)(Game1.toolHold % 600f * 0.08f), 8), barColor);
}
if (!Game1.IsFakedBlackScreen())
@@ -699,7 +695,7 @@ namespace StardewModdingAPI.Framework
}
if (Game1.currentLocation.LightLevel > 0f && Game1.timeOfDay < 2000)
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * Game1.currentLocation.LightLevel);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * Game1.currentLocation.LightLevel);
}
if (Game1.screenGlow)
{
@@ -711,51 +707,51 @@ namespace StardewModdingAPI.Framework
Game1.player.CurrentTool.draw(Game1.spriteBatch);
}
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp);
if (Game1.eventUp && Game1.currentLocation.currentEvent != null)
{
- foreach (NPC l in Game1.currentLocation.currentEvent.actors)
+ foreach (NPC n2 in Game1.currentLocation.currentEvent.actors)
{
- if (l.isEmoting)
+ if (n2.isEmoting)
{
- Vector2 emotePosition = l.getLocalPosition(Game1.viewport);
- if (l.NeedsBirdieEmoteHack())
+ Vector2 emotePosition = n2.getLocalPosition(Game1.viewport);
+ if (n2.NeedsBirdieEmoteHack())
{
emotePosition.X += 64f;
}
emotePosition.Y -= 140f;
- if (l.Age == 2)
+ if (n2.Age == 2)
{
emotePosition.Y += 32f;
}
- else if (l.Gender == 1)
+ else if (n2.Gender == 1)
{
emotePosition.Y += 10f;
}
- Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, emotePosition, new Microsoft.Xna.Framework.Rectangle(l.CurrentEmoteIndex * 16 % Game1.emoteSpriteSheet.Width, l.CurrentEmoteIndex * 16 / Game1.emoteSpriteSheet.Width * 16, 16, 16), Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, (float)l.getStandingY() / 10000f);
+ Game1.spriteBatch.Draw(Game1.emoteSpriteSheet, emotePosition, new Microsoft.Xna.Framework.Rectangle(n2.CurrentEmoteIndex * 16 % Game1.emoteSpriteSheet.Width, n2.CurrentEmoteIndex * 16 / Game1.emoteSpriteSheet.Width * 16, 16, 16), Color.White, 0f, Vector2.Zero, 4f, SpriteEffects.None, (float)n2.getStandingY() / 10000f);
}
}
}
Game1.spriteBatch.End();
if (Game1.drawLighting && !Game1.IsFakedBlackScreen())
{
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp);
Viewport vp = base.GraphicsDevice.Viewport;
- vp.Bounds = (target_screen?.Bounds ?? base.GraphicsDevice.PresentationParameters.Bounds);
+ vp.Bounds = target_screen?.Bounds ?? base.GraphicsDevice.PresentationParameters.Bounds;
base.GraphicsDevice.Viewport = vp;
float render_zoom = Game1.options.lightingQuality / 2;
if (this.useUnscaledLighting)
{
render_zoom /= Game1.options.zoomLevel;
}
- Game1.spriteBatch.Draw(Game1.lightmap, Vector2.Zero, Game1.lightmap.Bounds, Microsoft.Xna.Framework.Color.White, 0f, Vector2.Zero, render_zoom, SpriteEffects.None, 1f);
+ Game1.spriteBatch.Draw(Game1.lightmap, Vector2.Zero, Game1.lightmap.Bounds, Color.White, 0f, Vector2.Zero, render_zoom, SpriteEffects.None, 1f);
if (Game1.IsRainingHere() && (bool)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
{
- Game1.spriteBatch.Draw(Game1.staminaRect, vp.Bounds, Microsoft.Xna.Framework.Color.OrangeRed * 0.45f);
+ Game1.spriteBatch.Draw(Game1.staminaRect, vp.Bounds, Color.OrangeRed * 0.45f);
}
Game1.spriteBatch.End();
}
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
events.RenderedWorld.RaiseEmpty();
if (Game1.drawGrid)
{
@@ -763,11 +759,11 @@ namespace StardewModdingAPI.Framework
float startingY = -Game1.viewport.Y % 64;
for (int x = startingX; x < Game1.graphics.GraphicsDevice.Viewport.Width; x += 64)
{
- Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x, (int)startingY, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Red * 0.5f);
+ Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(x, (int)startingY, 1, Game1.graphics.GraphicsDevice.Viewport.Height), Color.Red * 0.5f);
}
for (float y = startingY; y < (float)Game1.graphics.GraphicsDevice.Viewport.Height; y += 64f)
{
- Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Microsoft.Xna.Framework.Color.Red * 0.5f);
+ Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Color.Red * 0.5f);
}
}
if (Game1.ShouldShowOnscreenUsernames() && Game1.currentLocation != null)
@@ -780,14 +776,14 @@ namespace StardewModdingAPI.Framework
}
if (!Game1.eventUp && Game1.farmEvent == null && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !this.takingMapScreenshot && Game1.isOutdoorMapSmallerThanViewport())
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, GameRunner.MaxTextureSize), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, GameRunner.MaxTextureSize)), Microsoft.Xna.Framework.Color.Black);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Microsoft.Xna.Framework.Color.Black);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, GameRunner.MaxTextureSize), Game1.graphics.GraphicsDevice.Viewport.Height), Color.Black);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Color.Black);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, GameRunner.MaxTextureSize)), Color.Black);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Color.Black);
}
Game1.spriteBatch.End();
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if ((Game1.displayHUD || Game1.eventUp) && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !Game1.freezeControls && !Game1.panMode && !Game1.HostPaused && !this.takingMapScreenshot)
{
events.RenderingHud.RaiseEmpty();
@@ -807,13 +803,13 @@ namespace StardewModdingAPI.Framework
}
Game1.spriteBatch.End();
Game1.PopUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
}
if (Game1.farmEvent != null)
{
Game1.farmEvent.draw(Game1.spriteBatch);
Game1.spriteBatch.End();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
}
Game1.PushUIMode();
if (Game1.dialogueUp && !Game1.nameSelectUp && !Game1.messagePause && (Game1.activeClickableMenu == null || !(Game1.activeClickableMenu is DialogueBox)) && !this.takingMapScreenshot)
@@ -822,35 +818,35 @@ namespace StardewModdingAPI.Framework
}
if (Game1.progressBar && !this.takingMapScreenshot)
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, Game1.dialogueWidth, 32), Microsoft.Xna.Framework.Color.LightGray);
- Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, (int)(Game1.pauseAccumulator / Game1.pauseTime * (float)Game1.dialogueWidth), 32), Microsoft.Xna.Framework.Color.DimGray);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, Game1.dialogueWidth, 32), Color.LightGray);
+ Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, (int)(Game1.pauseAccumulator / Game1.pauseTime * (float)Game1.dialogueWidth), 32), Color.DimGray);
}
Game1.spriteBatch.End();
Game1.PopUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if (Game1.eventUp && Game1.currentLocation != null && Game1.currentLocation.currentEvent != null)
{
Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch);
}
if (!Game1.IsFakedBlackScreen() && Game1.IsRainingHere() && Game1.currentLocation != null && (bool)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))
{
- Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Blue * 0.2f);
+ Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Blue * 0.2f);
}
if ((Game1.fadeToBlack || Game1.globalFade) && !Game1.menuUp && (!Game1.nameSelectUp || Game1.messagePause) && !this.takingMapScreenshot)
{
Game1.spriteBatch.End();
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((Game1.gameMode == 0) ? (1f - Game1.fadeToBlackAlpha) : Game1.fadeToBlackAlpha));
Game1.spriteBatch.End();
Game1.PopUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
}
else if (Game1.flashAlpha > 0f && !this.takingMapScreenshot)
{
if (Game1.options.screenFlash)
{
- Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.White * Math.Min(1f, Game1.flashAlpha));
+ Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.White * Math.Min(1f, Game1.flashAlpha));
}
Game1.flashAlpha -= 0.1f;
}
@@ -866,14 +862,14 @@ namespace StardewModdingAPI.Framework
}
Game1.spriteBatch.End();
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
foreach (TemporaryAnimatedSprite uiOverlayTempSprite in Game1.uiOverlayTempSprites)
{
uiOverlayTempSprite.draw(Game1.spriteBatch, localPosition: true);
}
Game1.spriteBatch.End();
Game1.PopUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
}
if (Game1.debugMode)
{
@@ -905,14 +901,14 @@ namespace StardewModdingAPI.Framework
sb.Append(Game1.getMouseY() + Game1.viewport.Y);
sb.Append(" debugOutput: ");
sb.Append(Game1.debugOutput);
- Game1.spriteBatch.DrawString(Game1.smallFont, sb, new Vector2(base.GraphicsDevice.Viewport.GetTitleSafeArea().X, base.GraphicsDevice.Viewport.GetTitleSafeArea().Y + Game1.smallFont.LineSpacing * 8), Microsoft.Xna.Framework.Color.Red, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
+ Game1.spriteBatch.DrawString(Game1.smallFont, sb, new Vector2(base.GraphicsDevice.Viewport.GetTitleSafeArea().X, base.GraphicsDevice.Viewport.GetTitleSafeArea().Y + Game1.smallFont.LineSpacing * 8), Color.Red, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
}
Game1.spriteBatch.End();
Game1.PushUIMode();
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
if (Game1.showKeyHelp && !this.takingMapScreenshot)
{
- Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2(64f, (float)(Game1.viewport.Height - 64 - (Game1.dialogueUp ? (192 + (Game1.isQuestion ? (Game1.questionChoices.Count * 64) : 0)) : 0)) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Microsoft.Xna.Framework.Color.LightGray, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
+ Game1.spriteBatch.DrawString(Game1.smallFont, Game1.keyHelpString, new Vector2(64f, (float)(Game1.viewport.Height - 64 - (Game1.dialogueUp ? (192 + (Game1.isQuestion ? (Game1.questionChoices.Count * 64) : 0)) : 0)) - Game1.smallFont.MeasureString(Game1.keyHelpString).Y), Color.LightGray, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9999999f);
}
if (Game1.activeClickableMenu != null && !this.takingMapScreenshot)
{
diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs
index 7dafc746..101e022a 100644
--- a/src/SMAPI/Framework/SModHooks.cs
+++ b/src/SMAPI/Framework/SModHooks.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using StardewValley;
namespace StardewModdingAPI.Framework
@@ -12,15 +13,20 @@ namespace StardewModdingAPI.Framework
/// <summary>A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</summary>
private readonly Action BeforeNewDayAfterFade;
+ /// <summary>Writes messages to the console.</summary>
+ private readonly IMonitor Monitor;
+
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="beforeNewDayAfterFade">A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</param>
- public SModHooks(Action beforeNewDayAfterFade)
+ /// <param name="monitor">Writes messages to the console.</param>
+ public SModHooks(Action beforeNewDayAfterFade, IMonitor monitor)
{
this.BeforeNewDayAfterFade = beforeNewDayAfterFade;
+ this.Monitor = monitor;
}
/// <summary>A hook invoked when <see cref="Game1.newDayAfterFade"/> is called.</summary>
@@ -30,5 +36,27 @@ namespace StardewModdingAPI.Framework
this.BeforeNewDayAfterFade?.Invoke();
action();
}
+
+ /// <summary>Start an asynchronous task for the game.</summary>
+ /// <param name="task">The task to start.</param>
+ /// <param name="id">A unique key which identifies the task.</param>
+ public override Task StartTask(Task task, string id)
+ {
+ this.Monitor.Log($"Synchronizing '{id}' task...");
+ task.RunSynchronously();
+ this.Monitor.Log(" task complete.");
+ return task;
+ }
+
+ /// <summary>Start an asynchronous task for the game.</summary>
+ /// <param name="task">The task to start.</param>
+ /// <param name="id">A unique key which identifies the task.</param>
+ public override Task<T> StartTask<T>(Task<T> task, string id)
+ {
+ this.Monitor.Log($"Synchronizing '{id}' task...");
+ task.RunSynchronously();
+ this.Monitor.Log(" task complete.");
+ return task;
+ }
}
}
diff --git a/src/SMAPI/GameFramework.cs b/src/SMAPI/GameFramework.cs
index 7670ce8f..a0154329 100644
--- a/src/SMAPI/GameFramework.cs
+++ b/src/SMAPI/GameFramework.cs
@@ -1,12 +1,15 @@
+using System;
+
namespace StardewModdingAPI
{
/// <summary>The game framework running the game.</summary>
public enum GameFramework
{
- /// <summary>The XNA Framework on Windows.</summary>
+ /// <summary>The XNA Framework, previously used on Windows.</summary>
+ [Obsolete("Stardew Valley no longer uses XNA Framework on any supported platform.")]
Xna,
- /// <summary>The MonoGame framework, usually on non-Windows platforms.</summary>
+ /// <summary>The MonoGame framework.</summary>
MonoGame
}
}
diff --git a/src/SMAPI/IAssetDataForMap.cs b/src/SMAPI/IAssetDataForMap.cs
index bfaba9ba..47a33de8 100644
--- a/src/SMAPI/IAssetDataForMap.cs
+++ b/src/SMAPI/IAssetDataForMap.cs
@@ -13,6 +13,7 @@ namespace StardewModdingAPI
/// <param name="source">The map from which to copy.</param>
/// <param name="sourceArea">The tile area within the source map to copy, or <c>null</c> for the entire source map size. This must be within the bounds of the <paramref name="source"/> map.</param>
/// <param name="targetArea">The tile area within the target map to overwrite, or <c>null</c> to patch the whole map. The original content within this area will be erased. This must be within the bounds of the existing map.</param>
- void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null);
+ /// <param name="patchMode">Indicates how the map should be patched.</param>
+ void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay);
}
}
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 7efd99a0..552bc000 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -226,13 +226,8 @@ namespace StardewModdingAPI.Metadata
** Buildings
****/
case "buildings\\houses": // Farm
- {
- var field = reflection.GetField<Texture2D>(typeof(Farm), nameof(Farm.houseTextures));
- field.SetValue(
- this.LoadAndDisposeIfNeeded(field.GetValue(), key)
- );
- return true;
- }
+ Farm.houseTextures = this.LoadAndDisposeIfNeeded(Farm.houseTextures, key);
+ return true;
case "buildings\\houses_paintmask": // Farm
{
@@ -447,10 +442,6 @@ namespace StardewModdingAPI.Metadata
Game1.objectSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "maps\\walls_and_floors": // Wallpaper
- Wallpaper.wallpaperTexture = content.Load<Texture2D>(key);
- return true;
-
/****
** Content\Minigames
****/
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index 76371e50..232e54ce 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -36,9 +36,6 @@ namespace StardewModdingAPI.Metadata
// rewrite for crossplatform compatibility
if (rewriteMods)
{
- if (platformChanged)
- yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));
-
// rewrite for Stardew Valley 1.5
yield return new FieldReplaceRewriter(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture));
yield return new FieldReplaceRewriter(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
@@ -48,9 +45,10 @@ namespace StardewModdingAPI.Metadata
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);
yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies);
- // rewrite for 64-bit mode
- // re-enable in Stardew Valley 1.5.5
- //yield return new ArchitectureAssemblyRewriter();
+ // rewrite for Stardew Valley 1.5.5
+ if (platformChanged)
+ yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));
+ yield return new ArchitectureAssemblyRewriter();
// detect Harmony & rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update)
yield return new HarmonyRewriter();
diff --git a/src/SMAPI/PatchMapMode.cs b/src/SMAPI/PatchMapMode.cs
new file mode 100644
index 00000000..1f1ac6a9
--- /dev/null
+++ b/src/SMAPI/PatchMapMode.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI
+{
+ /// <summary>Indicates how a map should be patched.</summary>
+ public enum PatchMapMode
+ {
+ /// <summary>Replace matching tiles. Target tiles missing in the source area are kept as-is.</summary>
+ Overlay,
+
+ /// <summary>Replace all tiles on layers that exist in the source map.</summary>
+ ReplaceByLayer,
+
+ /// <summary>Replace all tiles with the source map.</summary>
+ Replace
+ }
+}
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index 3f97e531..67ff8322 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -4,8 +4,8 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using StardewModdingAPI.Framework;
-using StardewModdingAPI.Toolkit.Framework;
using StardewModdingAPI.Toolkit.Serialization.Models;
+using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI
{
@@ -26,7 +26,7 @@ namespace StardewModdingAPI
/// <param name="args">The command-line arguments.</param>
public static void Main(string[] args)
{
- Console.Title = $"SMAPI {EarlyConstants.RawApiVersion} - {Console.Title}";
+ Console.Title = $"SMAPI {EarlyConstants.RawApiVersion}";
try
{
@@ -34,9 +34,10 @@ namespace StardewModdingAPI
Program.AssertGamePresent();
Program.AssertGameVersion();
Program.AssertSmapiVersions();
+ Program.AssertDepsJson();
Program.Start(args);
}
- catch (BadImageFormatException ex) when (ex.FileName == "StardewValley" || ex.FileName == "Stardew Valley") // don't use EarlyConstants.GameAssemblyName, since we want to check both possible names
+ catch (BadImageFormatException ex) when (ex.FileName == EarlyConstants.GameAssemblyName)
{
Console.WriteLine($"SMAPI failed to initialize because your game's {ex.FileName}.exe seems to be invalid.\nThis may be a pirated version which modified the executable in an incompatible way; if so, you can try a different download or buy a legitimate version.\n\nTechnical details:\n{ex}");
}
@@ -84,22 +85,10 @@ namespace StardewModdingAPI
}
catch (Exception ex)
{
- // unofficial 64-bit
- if (EarlyConstants.Platform == GamePlatform.Windows)
- {
- FileInfo linuxExecutable = new FileInfo(Path.Combine(EarlyConstants.ExecutionPath, "StardewValley.exe"));
- if (linuxExecutable.Exists && LowLevelEnvironmentUtility.Is64BitAssembly(linuxExecutable.FullName))
- Program.PrintErrorAndExit("Oops! You're running Stardew Valley in unofficial 64-bit mode, which is no longer supported. You can update to Stardew Valley 1.5.5 or later instead. See https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows for more info.");
- }
-
// file doesn't exist
if (!File.Exists(Path.Combine(EarlyConstants.ExecutionPath, $"{EarlyConstants.GameAssemblyName}.exe")))
Program.PrintErrorAndExit("Oops! SMAPI can't find the game. Make sure you're running StardewModdingAPI.exe in your game folder.");
- // Stardew Valley 1.5.5+
- if (File.Exists(Path.Combine(EarlyConstants.ExecutionPath, "Stardew Valley.dll")))
- Program.PrintErrorAndExit("Oops! You're running Stardew Valley 1.5.5 or later, but this version of SMAPI is only compatible up to Stardew Valley 1.5.4. Please check for a newer version of SMAPI: https://smapi.io.");
-
// can't load file
Program.PrintErrorAndExit(
message: "Oops! SMAPI couldn't load the game executable. The technical details below may have more info.",
@@ -143,6 +132,20 @@ namespace StardewModdingAPI
}
}
+ /// <summary>Assert that SMAPI's <c>StardewModdingAPI.deps.json</c> matches <c>Stardew Valley.deps.json</c>, fixing it if necessary.</summary>
+ /// <remarks>This is needed to resolve native DLLs like libSkiaSharp.</remarks>
+ private static void AssertDepsJson()
+ {
+ string sourcePath = Path.Combine(Constants.ExecutionPath, "Stardew Valley.deps.json");
+ string targetPath = Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.deps.json");
+
+ if (!File.Exists(targetPath) || FileUtilities.GetFileHash(sourcePath) != FileUtilities.GetFileHash(targetPath))
+ {
+ File.Copy(sourcePath, targetPath, overwrite: true);
+ Program.PrintErrorAndExit($"The '{Path.GetFileName(targetPath)}' file didn't match the game's version. SMAPI fixed it automatically, but you must restart SMAPI for the change to take effect.");
+ }
+ }
+
/// <summary>Initialize SMAPI and launch the game.</summary>
/// <param name="args">The command-line arguments.</param>
/// <remarks>This method is separate from <see cref="Main"/> because that can't contain any references to assemblies loaded by <see cref="CurrentDomain_AssemblyResolve"/> (e.g. via <see cref="Constants"/>), or Mono will incorrectly show an assembly resolution error before assembly resolution is set up.</remarks>
diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json
index 7b5625d6..e62c8880 100644
--- a/src/SMAPI/SMAPI.config.json
+++ b/src/SMAPI/SMAPI.config.json
@@ -76,8 +76,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future
/**
* The base URL for SMAPI's web API, used to perform update checks.
- * Note: the protocol will be changed to http:// on Linux/macOS due to OpenSSL issues with the
- * game's bundled Mono.
*/
"WebApiBaseUrl": "https://smapi.io/api/",
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index c147e7dc..b99028da 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -3,13 +3,16 @@
<AssemblyName>StardewModdingAPI</AssemblyName>
<RootNamespace>StardewModdingAPI</RootNamespace>
<Description>The modding API for Stardew Valley.</Description>
- <TargetFramework>net452</TargetFramework>
- <PlatformTarget>x86</PlatformTarget>
+ <TargetFramework>net5.0</TargetFramework>
+ <PlatformTarget>x64</PlatformTarget>
<OutputType>Exe</OutputType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<LargeAddressAware Condition="'$(OS)' == 'Windows_NT'">true</LargeAddressAware>
<ApplicationIcon>icon.ico</ApplicationIcon>
+
+ <!--copy dependency DLLs to bin folder so we can include them in installer bundle -->
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<Import Project="..\..\build\common.targets" />
@@ -19,43 +22,22 @@
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="MonoMod.Common" Version="21.6.21.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
- <PackageReference Include="Platonymous.TMXTile" Version="1.5.8" />
+ <PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
+ <PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
+ <PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="..\..\build\0Harmony.dll" Private="True" />
- <Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
+ <Reference Include="Stardew Valley" HintPath="$(GamePath)\Stardew Valley.dll" Private="False" />
<Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
- <Reference Include="System.Numerics" Private="True" />
- <Reference Include="System.Runtime.Caching" Private="True" />
+ <Reference Include="BmFont" HintPath="$(GamePath)\BmFont.dll" Private="False" />
<Reference Include="GalaxyCSharp" HintPath="$(GamePath)\GalaxyCSharp.dll" Private="False" />
<Reference Include="Lidgren.Network" HintPath="$(GamePath)\Lidgren.Network.dll" Private="False" />
+ <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
</ItemGroup>
- <!-- Windows only -->
- <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
- <Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
- <Reference Include="System.Windows.Forms" />
- </ItemGroup>
-
- <!-- Game framework -->
- <Choose>
- <When Condition="$(DefineConstants.Contains(SMAPI_FOR_XNA))">
- <ItemGroup>
- <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
- </ItemGroup>
- </When>
- <Otherwise>
- <ItemGroup>
- <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
- </ItemGroup>
- </Otherwise>
- </Choose>
-
<ItemGroup>
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />