using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; #if SMAPI_FOR_WINDOWS using System.Management; #endif using System.Runtime.InteropServices; namespace StardewModdingAPI.Toolkit.Utilities { /// Provides methods for fetching environment information. public static class EnvironmentUtility { /********* ** Fields *********/ /// The cached platform. private static Platform? CachedPlatform; /// Get the OS name from the system uname command. /// The buffer to fill with the resulting string. [DllImport("libc")] static extern int uname(IntPtr buffer); /********* ** Public methods *********/ /// Detect the current OS. public static Platform DetectPlatform() { return EnvironmentUtility.CachedPlatform ??= EnvironmentUtility.DetectPlatformImpl(); } /// Get the human-readable OS name and version. /// The current platform. [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] public static string GetFriendlyPlatformName(Platform platform) { #if SMAPI_FOR_WINDOWS try { return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") .Get() .Cast() .Select(entry => entry.GetPropertyValue("Caption").ToString()) .FirstOrDefault(); } catch { } #endif string name = Environment.OSVersion.ToString(); switch (platform) { case Platform.Android: name = $"Android {name}"; break; case Platform.Mac: name = $"MacOS {name}"; break; } return name; } /// Get the name of the Stardew Valley executable. /// The current platform. public static string GetExecutableName(Platform platform) { return platform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"; } /// Get whether the platform uses Mono. /// The current platform. public static bool IsMono(this Platform platform) { return platform == Platform.Linux || platform == Platform.Mac; } /********* ** Private methods *********/ /// Detect the current OS. 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; } } /// Detect whether the code is running on Android. /// /// This code is derived from https://stackoverflow.com/a/47521647/262123. It detects Android by calling the /// getprop system command to check for an Android-specific property. /// private static bool IsRunningAndroid() { using Process process = new Process { StartInfo = { FileName = "getprop", Arguments = "ro.build.user", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true } }; try { process.Start(); string output = process.StandardOutput.ReadToEnd(); return !string.IsNullOrWhiteSpace(output); } catch { return false; } } /// Detect whether the code is running on Mac. /// /// This code is derived from the Mono project (see System.Windows.Forms/System.Windows.Forms/XplatUI.cs). It detects Mac by calling the /// uname system command and checking the response, which is always 'Darwin' for MacOS. /// private static bool IsRunningMac() { IntPtr buffer = IntPtr.Zero; try { buffer = Marshal.AllocHGlobal(8192); if (EnvironmentUtility.uname(buffer) == 0) { string os = Marshal.PtrToStringAnsi(buffer); return os == "Darwin"; } return false; } catch { return false; // default to Linux } finally { if (buffer != IntPtr.Zero) Marshal.FreeHGlobal(buffer); } } } }