diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs index 47d3cb139e..de602affeb 100644 --- a/OpenRA.Game/ExternalMods.cs +++ b/OpenRA.Game/ExternalMods.cs @@ -47,7 +47,7 @@ namespace OpenRA // If the player has defined a local support directory (in the game directory) // then this will override both the regular and system support dirs - var sources = new[] { Platform.SystemSupportDir, Platform.SupportDir }; + var sources = new[] { Platform.GetSupportDir(SupportDirType.System), Platform.GetSupportDir(SupportDirType.User) }; foreach (var source in sources.Distinct()) { var metadataPath = Path.Combine(source, "ModMetadata"); @@ -112,10 +112,10 @@ namespace OpenRA var sources = new List(); if (registration.HasFlag(ModRegistration.System)) - sources.Add(Platform.SystemSupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.System)); if (registration.HasFlag(ModRegistration.User)) - sources.Add(Platform.SupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.User)); // Make sure the mod is available for this session, even if saving it fails LoadMod(yaml.First().Value, forceRegistration: true); @@ -148,10 +148,10 @@ namespace OpenRA { var sources = new List(); if (registration.HasFlag(ModRegistration.System)) - sources.Add(Platform.SystemSupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.System)); if (registration.HasFlag(ModRegistration.User)) - sources.Add(Platform.SupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.User)); var activeModKey = ExternalMod.MakeKey(activeMod); foreach (var source in sources.Distinct()) @@ -207,10 +207,10 @@ namespace OpenRA { var sources = new List(); if (registration.HasFlag(ModRegistration.System)) - sources.Add(Platform.SystemSupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.System)); if (registration.HasFlag(ModRegistration.User)) - sources.Add(Platform.SupportDir); + sources.Add(Platform.GetSupportDir(SupportDirType.User)); var key = ExternalMod.MakeKey(mod); mods.Remove(key); diff --git a/OpenRA.Game/Platform.cs b/OpenRA.Game/Platform.cs index 10bc8a4e05..484c3c87df 100644 --- a/OpenRA.Game/Platform.cs +++ b/OpenRA.Game/Platform.cs @@ -18,6 +18,8 @@ namespace OpenRA { public enum PlatformType { Unknown, Windows, OSX, Linux } + public enum SupportDirType { System, ModernUser, LegacyUser, User } + public static class Platform { public const string SupportDirPrefix = "^"; @@ -26,6 +28,12 @@ namespace OpenRA static Lazy currentPlatform = Exts.Lazy(GetCurrentPlatform); + static bool supportDirInitialized; + static string systemSupportPath; + static string legacyUserSupportPath; + static string modernUserSupportPath; + static string userSupportPath; + static PlatformType GetCurrentPlatform() { if (Environment.OSVersion.Platform == PlatformID.Win32NT) @@ -66,18 +74,91 @@ namespace OpenRA /// /// Directory containing user-specific support files (settings, maps, replays, game data, etc). - /// The directory will automatically be created if it does not exist when this is queried. /// - public static string SupportDir { get { return supportDir.Value; } } - static Lazy supportDir = Exts.Lazy(GetSupportDir); - static string supportDirOverride; + public static string SupportDir { get { return 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"); + legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA"); + 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"); + + systemSupportPath = "/Library/Application Support/OpenRA/"; + break; + } + + case PlatformType.Linux: + { + legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra"); + + var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + if (string.IsNullOrEmpty(xdgConfigHome)) + xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config"); + + modernUserSupportPath = Path.Combine(xdgConfigHome, "openra"); + systemSupportPath = "/var/games/openra/"; + + break; + } + + default: + { + modernUserSupportPath = legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra"); + 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(GameDir, "Support"); + if (Directory.Exists(localSupportDir)) + userSupportPath = systemSupportPath = localSupportDir + Path.DirectorySeparatorChar; + + // Use the fallback directory if it exists and the preferred one does not + else if (!Directory.Exists(modernUserSupportPath) && Directory.Exists(legacyUserSupportPath)) + userSupportPath = legacyUserSupportPath + Path.DirectorySeparatorChar; + else + userSupportPath = modernUserSupportPath + Path.DirectorySeparatorChar; + + supportDirInitialized = true; + } /// /// Specify a custom support directory that already exists on the filesystem. - /// MUST be called before Platform.SupportDir is first accessed. + /// Cannot be called after Platform.SupportDir / GetSupportDir have been accessed. /// 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); @@ -85,92 +166,8 @@ namespace OpenRA !path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)) path += Path.DirectorySeparatorChar; - supportDirOverride = path; - } - - static string GetSupportDir() - { - // Use the custom override if it has been defined - if (supportDirOverride != null) - return supportDirOverride; - - // Use a local directory in the game root if it exists (shared with the system support dir) - var localSupportDir = Path.Combine(GameDir, "Support"); - if (Directory.Exists(localSupportDir)) - return localSupportDir + Path.DirectorySeparatorChar; - - // The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions - string preferredSupportDir; - string fallbackSupportDir; - switch (CurrentPlatform) - { - case PlatformType.Windows: - { - preferredSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA"); - fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA"); - break; - } - - case PlatformType.OSX: - { - preferredSupportDir = fallbackSupportDir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.Personal), - "Library", "Application Support", "OpenRA"); - break; - } - - case PlatformType.Linux: - { - fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra"); - - var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); - if (string.IsNullOrEmpty(xdgConfigHome)) - xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config"); - - preferredSupportDir = Path.Combine(xdgConfigHome, "openra"); - - break; - } - - default: - { - preferredSupportDir = fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra"); - break; - } - } - - // Use the fallback directory if it exists and the preferred one does not - if (!Directory.Exists(preferredSupportDir) && Directory.Exists(fallbackSupportDir)) - return fallbackSupportDir + Path.DirectorySeparatorChar; - - return preferredSupportDir + Path.DirectorySeparatorChar; - } - - /// - /// Directory containing system-wide support files (mod metadata). - /// This directory is not guaranteed to exist or be writable. - /// Consumers are expected to check the validity of the returned value, and - /// fall back to the user support directory if necessary. - /// - public static string SystemSupportDir { get { return systemSupportDir.Value; } } - static Lazy systemSupportDir = Exts.Lazy(GetSystemSupportDir); - - static string GetSystemSupportDir() - { - // Use a local directory in the game root if it exists (shared with the system support dir) - var localSupportDir = Path.Combine(GameDir, "Support"); - if (Directory.Exists(localSupportDir)) - return localSupportDir + Path.DirectorySeparatorChar; - - switch (CurrentPlatform) - { - case PlatformType.Windows: - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar; - case PlatformType.OSX: - return "/Library/Application Support/OpenRA/"; - default: - return "/var/games/openra/"; - } + InitializeSupportDir(); + userSupportPath = path; } public static string GameDir