using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
#if SMAPI_FOR_WINDOWS
using System.Management;
#endif
using System.Runtime.InteropServices;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Toolkit.Framework
{
/// Provides low-level methods for fetching environment information.
/// This is used by the SMAPI core before the toolkit DLL is available; most code should use instead.
internal static class LowLevelEnvironmentUtility
{
/*********
** Fields
*********/
/// 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 string DetectPlatform()
{
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);
}
}
/// 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(string platform)
{
#if SMAPI_FOR_WINDOWS
try
{
string? result = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem")
.Get()
.Cast()
.Select(entry => entry.GetPropertyValue("Caption").ToString())
.FirstOrDefault();
return result ?? "Windows";
}
catch { }
#endif
string name = Environment.OSVersion.ToString();
switch (platform)
{
case nameof(Platform.Android):
name = $"Android {name}";
break;
case nameof(Platform.Mac):
name = $"macOS {name}";
break;
}
return name;
}
/// Get whether an executable is 64-bit.
/// The absolute path to the assembly file.
public static bool Is64BitAssembly(string path)
{
return AssemblyName.GetAssemblyName(path).ProcessorArchitecture != ProcessorArchitecture.X86;
}
/*********
** Private methods
*********/
/// 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()
{
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 macOS.
///
/// This code is derived from the Mono project (see System.Windows.Forms/System.Windows.Forms/XplatUI.cs). It detects macOS 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 (LowLevelEnvironmentUtility.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);
}
}
}
}