Files
OpenRA/OpenRA.Game/Platform.cs
RoosterDragon 225bcbbd22 Normalize all support dir paths to end with a directory separator.
Previously, some paths used a separator and some did not. This broke some de-duplication logic in ExternalMods which tried to enumerate distinct paths but would end up running logic on the same directory more than one as it was provided both with and without a trailing directory separator. By normalizing the path this logic now works.
2021-11-13 22:24:43 +01:00

254 lines
8.0 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace OpenRA
{
public enum PlatformType { Unknown, Windows, OSX, Linux }
public enum SupportDirType { System, ModernUser, LegacyUser, User }
public static class Platform
{
public static PlatformType CurrentPlatform => currentPlatform.Value;
public static readonly Guid SessionGUID = Guid.NewGuid();
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);
static bool engineDirAccessed;
static string engineDir;
static bool supportDirInitialized;
static string systemSupportPath;
static string legacyUserSupportPath;
static string modernUserSupportPath;
static string userSupportPath;
static PlatformType GetCurrentPlatform()
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
return PlatformType.Windows;
try
{
var psi = new ProcessStartInfo("uname", "-s");
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
var p = Process.Start(psi);
var kernelName = p.StandardOutput.ReadToEnd();
if (kernelName.Contains("Darwin"))
return PlatformType.OSX;
return PlatformType.Linux;
}
catch { }
return PlatformType.Unknown;
}
public static string RuntimeVersion
{
get
{
var mono = Type.GetType("Mono.Runtime");
if (mono == null)
return $".NET CLR {Environment.Version}";
var version = mono.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (version == null)
return $"Mono (unknown version) CLR {Environment.Version}";
return $"Mono {version.Invoke(null, null)} CLR {Environment.Version}";
}
}
/// <summary>
/// Directory containing user-specific support files (settings, maps, replays, game data, etc).
/// </summary>
public static string SupportDir => GetSupportDir(SupportDirType.User);
public static string GetSupportDir(SupportDirType type)
{
if (!supportDirInitialized)
InitializeSupportDir();
switch (type)
{
case SupportDirType.System: return systemSupportPath;
case SupportDirType.LegacyUser: return legacyUserSupportPath;
case SupportDirType.ModernUser: return modernUserSupportPath;
default: return userSupportPath;
}
}
static void InitializeSupportDir()
{
// The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions
switch (CurrentPlatform)
{
case PlatformType.Windows:
{
modernUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA") + Path.DirectorySeparatorChar;
systemSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
break;
}
case PlatformType.OSX:
{
modernUserSupportPath = legacyUserSupportPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"Library", "Application Support", "OpenRA") + Path.DirectorySeparatorChar;
systemSupportPath = "/Library/Application Support/OpenRA/";
break;
}
case PlatformType.Linux:
{
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra") + Path.DirectorySeparatorChar;
var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
if (string.IsNullOrEmpty(xdgConfigHome))
xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config") + Path.DirectorySeparatorChar;
modernUserSupportPath = Path.Combine(xdgConfigHome, "openra") + Path.DirectorySeparatorChar;
systemSupportPath = "/var/games/openra/";
break;
}
default:
{
modernUserSupportPath = legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra") + Path.DirectorySeparatorChar;
systemSupportPath = "/var/games/openra/";
break;
}
}
// Use a local directory in the game root if it exists (shared with the system support dir)
var localSupportDir = Path.Combine(EngineDir, "Support") + Path.DirectorySeparatorChar;
if (Directory.Exists(localSupportDir))
userSupportPath = systemSupportPath = localSupportDir;
// Use the fallback directory if it exists and the preferred one does not
else if (!Directory.Exists(modernUserSupportPath) && Directory.Exists(legacyUserSupportPath))
userSupportPath = legacyUserSupportPath;
else
userSupportPath = modernUserSupportPath;
supportDirInitialized = true;
}
/// <summary>
/// Specify a custom support directory that already exists on the filesystem.
/// Cannot be called after Platform.SupportDir / GetSupportDir have been accessed.
/// </summary>
public static void OverrideSupportDir(string path)
{
if (supportDirInitialized)
throw new InvalidOperationException("Attempted to override user support directory after it has already been accessed.");
if (!Directory.Exists(path))
throw new DirectoryNotFoundException(path);
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) &&
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
path += Path.DirectorySeparatorChar;
InitializeSupportDir();
userSupportPath = path;
}
public static string EngineDir
{
get
{
// Engine directory defaults to the location of the binaries,
// unless OverrideGameDir is called during startup.
if (!engineDirAccessed)
engineDir = BinDir;
engineDirAccessed = true;
return engineDir;
}
}
/// <summary>
/// Specify a custom engine directory that already exists on the filesystem.
/// Cannot be called after Platform.EngineDir has been accessed.
/// </summary>
public static void OverrideEngineDir(string path)
{
if (engineDirAccessed)
throw new InvalidOperationException("Attempted to override engine directory after it has already been accessed.");
// Note: Relative paths are interpreted as being relative to BinDir, not the current working dir.
if (!Path.IsPathRooted(path))
path = Path.Combine(BinDir, path);
if (!Directory.Exists(path))
throw new DirectoryNotFoundException(path);
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) &&
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
path += Path.DirectorySeparatorChar;
engineDirAccessed = true;
engineDir = path;
}
public static string BinDir
{
get
{
var dir = AppDomain.CurrentDomain.BaseDirectory;
// Add trailing DirectorySeparator for some buggy AppPool hosts
if (!dir.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
dir += Path.DirectorySeparatorChar;
return dir;
}
}
/// <summary>Replaces special character prefixes with full paths.</summary>
public static string ResolvePath(string path)
{
path = path.TrimEnd(' ', '\t');
if (path == "^SupportDir")
return SupportDir;
if (path == "^EngineDir")
return EngineDir;
if (path == "^BinDir")
return BinDir;
if (path.StartsWith("^SupportDir|", StringComparison.Ordinal))
path = SupportDir + path.Substring(12);
if (path.StartsWith("^EngineDir|", StringComparison.Ordinal))
path = EngineDir + path.Substring(11);
if (path.StartsWith("^BinDir|", StringComparison.Ordinal))
path = BinDir + path.Substring(8);
return path;
}
}
}