add EarlyConstants for constants needed before external DLLs are loaded
This commit is contained in:
parent
625c41c0ea
commit
76c926c396
|
@ -6,18 +6,17 @@ using System.Linq;
|
|||
using System.Management;
|
||||
#endif
|
||||
using System.Runtime.InteropServices;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Utilities
|
||||
namespace StardewModdingAPI.Toolkit.Framework
|
||||
{
|
||||
/// <summary>Provides methods for fetching environment information.</summary>
|
||||
public static class EnvironmentUtility
|
||||
/// <summary>Provides low-level methods for fetching environment information.</summary>
|
||||
/// <remarks>This is used by the SMAPI core before the toolkit DLL is available; most code should use <see cref="EnvironmentUtility"/> instead.</remarks>
|
||||
internal static class LowLevelEnvironmentUtility
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The cached platform.</summary>
|
||||
private static Platform? CachedPlatform;
|
||||
|
||||
/// <summary>Get the OS name from the system uname command.</summary>
|
||||
/// <param name="buffer">The buffer to fill with the resulting string.</param>
|
||||
[DllImport("libc")]
|
||||
|
@ -28,16 +27,32 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Detect the current OS.</summary>
|
||||
public static Platform DetectPlatform()
|
||||
public static string DetectPlatform()
|
||||
{
|
||||
return EnvironmentUtility.CachedPlatform ??= EnvironmentUtility.DetectPlatformImpl();
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
case PlatformID.MacOSX:
|
||||
return nameof(Platform.Mac);
|
||||
|
||||
case PlatformID.Unix when LowLevelEnvironmentUtility.IsRunningAndroid():
|
||||
return nameof(Platform.Android);
|
||||
|
||||
case PlatformID.Unix when LowLevelEnvironmentUtility.IsRunningMac():
|
||||
return nameof(Platform.Mac);
|
||||
|
||||
case PlatformID.Unix:
|
||||
return nameof(Platform.Linux);
|
||||
|
||||
default:
|
||||
return nameof(Platform.Windows);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Get the human-readable OS name and version.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
[SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
|
||||
public static string GetFriendlyPlatformName(Platform platform)
|
||||
public static string GetFriendlyPlatformName(string platform)
|
||||
{
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
try
|
||||
|
@ -54,11 +69,11 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
string name = Environment.OSVersion.ToString();
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.Android:
|
||||
case nameof(Platform.Android):
|
||||
name = $"Android {name}";
|
||||
break;
|
||||
|
||||
case Platform.Mac:
|
||||
case nameof(Platform.Mac):
|
||||
name = $"MacOS {name}";
|
||||
break;
|
||||
}
|
||||
|
@ -67,46 +82,24 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
|
||||
/// <summary>Get the name of the Stardew Valley executable.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
public static string GetExecutableName(Platform platform)
|
||||
public static string GetExecutableName(string platform)
|
||||
{
|
||||
return platform == Platform.Windows
|
||||
return platform == nameof(Platform.Windows)
|
||||
? "Stardew Valley.exe"
|
||||
: "StardewValley.exe";
|
||||
}
|
||||
|
||||
/// <summary>Get whether the platform uses Mono.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
public static bool IsMono(this Platform platform)
|
||||
public static bool IsMono(string platform)
|
||||
{
|
||||
return platform == Platform.Linux || platform == Platform.Mac;
|
||||
return platform == nameof(Platform.Linux) || platform == nameof(Platform.Mac);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Detect the current OS.</summary>
|
||||
private static Platform DetectPlatformImpl()
|
||||
{
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
case PlatformID.MacOSX:
|
||||
return Platform.Mac;
|
||||
|
||||
case PlatformID.Unix when EnvironmentUtility.IsRunningAndroid():
|
||||
return Platform.Android;
|
||||
|
||||
case PlatformID.Unix when EnvironmentUtility.IsRunningMac():
|
||||
return Platform.Mac;
|
||||
|
||||
case PlatformID.Unix:
|
||||
return Platform.Linux;
|
||||
|
||||
default:
|
||||
return Platform.Windows;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Detect whether the code is running on Android.</summary>
|
||||
/// <remarks>
|
||||
/// This code is derived from https://stackoverflow.com/a/47521647/262123. It detects Android by calling the
|
||||
|
@ -149,7 +142,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
|||
try
|
||||
{
|
||||
buffer = Marshal.AllocHGlobal(8192);
|
||||
if (EnvironmentUtility.uname(buffer) == 0)
|
||||
if (LowLevelEnvironmentUtility.uname(buffer) == 0)
|
||||
{
|
||||
string os = Marshal.PtrToStringAnsi(buffer);
|
||||
return os == "Darwin";
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Utilities
|
||||
{
|
||||
/// <summary>Provides methods for fetching environment information.</summary>
|
||||
public static class EnvironmentUtility
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The cached platform.</summary>
|
||||
private static Platform? CachedPlatform;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Detect the current OS.</summary>
|
||||
public static Platform DetectPlatform()
|
||||
{
|
||||
Platform? platform = EnvironmentUtility.CachedPlatform;
|
||||
|
||||
if (platform == null)
|
||||
{
|
||||
string rawPlatform = LowLevelEnvironmentUtility.DetectPlatform();
|
||||
EnvironmentUtility.CachedPlatform = platform = (Platform)Enum.Parse(typeof(Platform), rawPlatform, ignoreCase: true);
|
||||
}
|
||||
|
||||
return platform.Value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Get the human-readable OS name and version.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
[SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
|
||||
public static string GetFriendlyPlatformName(Platform platform)
|
||||
{
|
||||
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 the platform uses Mono.</summary>
|
||||
/// <param name="platform">The current platform.</param>
|
||||
public static bool IsMono(this Platform platform)
|
||||
{
|
||||
return LowLevelEnvironmentUtility.IsMono(platform.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,42 @@ using System.Reflection;
|
|||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Toolkit.Framework;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>Contains constants that are accessed before the game itself has been loaded.</summary>
|
||||
/// <remarks>Most code should use <see cref="Constants"/> instead of this class directly.</remarks>
|
||||
internal static class EarlyConstants
|
||||
{
|
||||
//
|
||||
// Note: this class *must not* depend on any external DLL beyond .NET Framework itself.
|
||||
// That includes the game or SMAPI toolkit, since it's accessed before those are loaded.
|
||||
//
|
||||
// Adding an external dependency may seem to work in some cases, but will prevent SMAPI
|
||||
// from showing a human-readable error if the game isn't available. To test this, just
|
||||
// rename "Stardew Valley.exe" in the game folder; you should see an error like "Oops!
|
||||
// SMAPI can't find the game", not a technical exception.
|
||||
//
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The path to the game folder.</summary>
|
||||
public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
|
||||
public static readonly string InternalFilesPath = Path.Combine(EarlyConstants.ExecutionPath, "smapi-internal");
|
||||
|
||||
/// <summary>The target game platform.</summary>
|
||||
internal static GamePlatform Platform { get; } = (GamePlatform)Enum.Parse(typeof(GamePlatform), LowLevelEnvironmentUtility.DetectPlatform());
|
||||
|
||||
/// <summary>The game's assembly name.</summary>
|
||||
internal static string GameAssemblyName => EarlyConstants.Platform == GamePlatform.Windows ? "Stardew Valley" : "StardewValley";
|
||||
}
|
||||
|
||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||
public static class Constants
|
||||
{
|
||||
|
@ -29,10 +60,10 @@ namespace StardewModdingAPI
|
|||
public static ISemanticVersion MaximumGameVersion { get; } = null;
|
||||
|
||||
/// <summary>The target game platform.</summary>
|
||||
public static GamePlatform TargetPlatform => (GamePlatform)Constants.Platform;
|
||||
public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform;
|
||||
|
||||
/// <summary>The path to the game folder.</summary>
|
||||
public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
public static string ExecutionPath { get; } = EarlyConstants.ExecutionPath;
|
||||
|
||||
/// <summary>The directory path containing Stardew Valley's app data.</summary>
|
||||
public static string DataPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley");
|
||||
|
@ -67,7 +98,7 @@ namespace StardewModdingAPI
|
|||
internal const string GamePerformanceCounterName = "<StardewValley>";
|
||||
|
||||
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
|
||||
internal static readonly string InternalFilesPath = Program.DllSearchPath;
|
||||
internal static readonly string InternalFilesPath = EarlyConstants.InternalFilesPath;
|
||||
|
||||
/// <summary>The file path for the SMAPI configuration file.</summary>
|
||||
internal static string ApiConfigPath => Path.Combine(Constants.InternalFilesPath, "config.json");
|
||||
|
@ -105,11 +136,8 @@ namespace StardewModdingAPI
|
|||
/// <summary>The game's current semantic version.</summary>
|
||||
internal static ISemanticVersion GameVersion { get; } = new GameVersion(Game1.version);
|
||||
|
||||
/// <summary>The target game platform.</summary>
|
||||
internal static Platform Platform { get; } = EnvironmentUtility.DetectPlatform();
|
||||
|
||||
/// <summary>The game's assembly name.</summary>
|
||||
internal static string GameAssemblyName => Constants.Platform == Platform.Windows ? "Stardew Valley" : "StardewValley";
|
||||
/// <summary>The target game platform as a SMAPI toolkit constant.</summary>
|
||||
internal static Platform Platform { get; } = (Platform)Constants.TargetPlatform;
|
||||
|
||||
/// <summary>The language code for non-translated mod assets.</summary>
|
||||
internal static LocalizedContentManager.LanguageCode DefaultLanguage { get; } = LocalizedContentManager.LanguageCode.en;
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
#endif
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
@ -18,9 +14,7 @@ namespace StardewModdingAPI
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The absolute path to search for SMAPI's internal DLLs.</summary>
|
||||
/// <remarks>We can't use <see cref="Constants.ExecutionPath"/> directly, since <see cref="Constants"/> depends on DLLs loaded from this folder.</remarks>
|
||||
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")]
|
||||
internal static readonly string DllSearchPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "smapi-internal");
|
||||
internal static readonly string DllSearchPath = EarlyConstants.InternalFilesPath;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -37,10 +31,9 @@ namespace StardewModdingAPI
|
|||
Program.AssertGameVersion();
|
||||
Program.Start(args);
|
||||
}
|
||||
catch (BadImageFormatException ex) when (ex.FileName == "StardewValley" || ex.FileName == "Stardew Valley") // NOTE: don't use StardewModdingAPI.Constants here, assembly resolution isn't hooked up at this point
|
||||
catch (BadImageFormatException ex) when (ex.FileName == "StardewValley" || ex.FileName == "Stardew Valley") // don't use EarlyConstants.GameAssemblyName, since we want to check both possible names
|
||||
{
|
||||
string executableName = Program.GetExecutableAssemblyName();
|
||||
Console.WriteLine($"SMAPI failed to initialize because your game's {executableName}.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}");
|
||||
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}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -76,11 +69,10 @@ namespace StardewModdingAPI
|
|||
}
|
||||
|
||||
/// <summary>Assert that the game is available.</summary>
|
||||
/// <remarks>This must be checked *before* any references to <see cref="Constants"/>, and this method should not reference <see cref="Constants"/> itself to avoid errors in Mono.</remarks>
|
||||
/// <remarks>This must be checked *before* any references to <see cref="Constants"/>, and this method should not reference <see cref="Constants"/> itself to avoid errors in Mono or when the game isn't present.</remarks>
|
||||
private static void AssertGamePresent()
|
||||
{
|
||||
string gameAssemblyName = Program.GetExecutableAssemblyName();
|
||||
if (Type.GetType($"StardewValley.Game1, {gameAssemblyName}", throwOnError: false) == null)
|
||||
if (Type.GetType($"StardewValley.Game1, {EarlyConstants.GameAssemblyName}", throwOnError: false) == null)
|
||||
Program.PrintErrorAndExit("Oops! SMAPI can't find the game. Make sure you're running StardewModdingAPI.exe in your game folder. See the readme.txt file for details.");
|
||||
}
|
||||
|
||||
|
@ -100,14 +92,6 @@ namespace StardewModdingAPI
|
|||
// max version
|
||||
else if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion))
|
||||
Program.PrintErrorAndExit($"Oops! You're running Stardew Valley {Constants.GameVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI: https://smapi.io.");
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Get the game's executable assembly name.</summary>
|
||||
private static string GetExecutableAssemblyName()
|
||||
{
|
||||
Platform platform = EnvironmentUtility.DetectPlatform();
|
||||
return platform == Platform.Windows ? "Stardew Valley" : "StardewValley";
|
||||
}
|
||||
|
||||
/// <summary>Initialize SMAPI and launch the game.</summary>
|
||||
|
|
|
@ -59,8 +59,11 @@
|
|||
<ItemGroup>
|
||||
<Content Include="SMAPI.config.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="..\SMAPI.Web\wwwroot\SMAPI.metadata.json" Link="SMAPI.metadata.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
<None Update="i18n\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="steam_appid.txt" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
<Compile Include="..\SMAPI.Toolkit\Framework\LowLevelEnvironmentUtility.cs" Link="Framework\Utilities\LowLevelEnvironmentUtility.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
|
|
Loading…
Reference in New Issue