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);
}
}
}
}