diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs | 69 | ||||
-rw-r--r-- | src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs | 57 | ||||
-rw-r--r-- | src/SMAPI/Constants.cs | 44 | ||||
-rw-r--r-- | src/SMAPI/Program.cs | 26 | ||||
-rw-r--r-- | src/SMAPI/SMAPI.csproj | 3 |
5 files changed, 132 insertions, 67 deletions
diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 1e490448..b01d8b21 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -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"; diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs new file mode 100644 index 00000000..4ef578f7 --- /dev/null +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -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()); + } + } +} diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index c1c99150..b7977fb7 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -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; diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index a8f20c69..05251070 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -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> diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index a3dbf52f..7d2b8199 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -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" /> |