diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Installer/Framework/InstallerContext.cs | 103 | ||||
-rw-r--r-- | src/SMAPI.Installer/InteractiveInstaller.cs | 124 | ||||
-rw-r--r-- | src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs | 48 |
3 files changed, 179 insertions, 96 deletions
diff --git a/src/SMAPI.Installer/Framework/InstallerContext.cs b/src/SMAPI.Installer/Framework/InstallerContext.cs new file mode 100644 index 00000000..7531eaee --- /dev/null +++ b/src/SMAPI.Installer/Framework/InstallerContext.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using Microsoft.Win32; +using StardewModdingAPI.Toolkit; +using StardewModdingAPI.Toolkit.Framework.GameScanning; +using StardewModdingAPI.Toolkit.Utilities; + +namespace StardewModdingAPI.Installer.Framework +{ + /// <summary>The installer context.</summary> + internal class InstallerContext + { + /********* + ** Fields + *********/ + /// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary> + private readonly Version Windows7Version = new Version(6, 1); + + /// <summary>The underlying toolkit game scanner.</summary> + private readonly GameScanner GameScanner = new GameScanner(); + + + /********* + ** Accessors + *********/ + /// <summary>The current OS.</summary> + public Platform Platform { get; } + + /// <summary>The human-readable OS name and version.</summary> + public string PlatformName { get; } + + /// <summary>The name of the Stardew Valley executable.</summary> + public string ExecutableName { get; } + + /// <summary>Whether the installer is running on Windows.</summary> + public bool IsWindows => this.Platform == Platform.Windows; + + /// <summary>Whether the installer is running on a Unix OS (including Linux or MacOS).</summary> + public bool IsUnix => !this.IsWindows; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public InstallerContext() + { + this.Platform = EnvironmentUtility.DetectPlatform(); + this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform); + this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform); + } + + /// <summary>Get the installer's version number.</summary> + public ISemanticVersion GetInstallerVersion() + { + var raw = this.GetType().Assembly.GetName().Version; + return new SemanticVersion(raw); + } + + /// <summary>Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows.</summary> + /// <exception cref="NotSupportedException">The current platform is not Windows.</exception> + public bool HasNetFramework45() + { + switch (this.Platform) + { + case Platform.Windows: + using (RegistryKey versionKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full")) + return versionKey?.GetValue("Release") != null; // .NET Framework 4.5+ + + default: + throw new NotSupportedException("The installed .NET Framework version can only be checked on Windows."); + } + } + + /// <summary>Get whether the current system has XNA Framework installed. This only applies on Windows.</summary> + /// <exception cref="NotSupportedException">The current platform is not Windows.</exception> + public bool HasXna() + { + switch (this.Platform) + { + case Platform.Windows: + using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\XNA\Framework")) + return key != null; // XNA Framework 4.0+ + + default: + throw new NotSupportedException("The installed XNA Framework version can only be checked on Windows."); + } + } + + /// <summary>Whether the current OS supports newer versions of .NET Framework.</summary> + public bool CanInstallLatestNetFramework() + { + return Environment.OSVersion.Version >= this.Windows7Version; // Windows 7+ + } + + /// <summary>Get whether a folder seems to contain the game files.</summary> + /// <param name="dir">The folder to check.</param> + public bool LooksLikeGameFolder(DirectoryInfo dir) + { + return this.GameScanner.LooksLikeGameFolder(dir); + } + } +} diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index d0ef0b8d..035ec382 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using Microsoft.Win32; +using System.Reflection; using StardewModdingApi.Installer.Enums; using StardewModdingAPI.Installer.Framework; using StardewModdingAPI.Internal.ConsoleWriting; @@ -25,12 +25,8 @@ namespace StardewModdingApi.Installer /// <summary>The absolute path to the directory containing the files to copy into the game folder.</summary> private readonly string BundlePath; - /// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary> - private readonly Version Windows7Version = new Version(6, 1); - /// <summary>The mod IDs which the installer should allow as bundled mods.</summary> - private readonly string[] BundledModIDs = new[] - { + private readonly string[] BundledModIDs = { "SMAPI.SaveBackup", "SMAPI.ConsoleCommands" }; @@ -129,22 +125,22 @@ namespace StardewModdingApi.Installer ** Get basic info & set window title ****/ ModToolkit toolkit = new ModToolkit(); - Platform platform = EnvironmentUtility.DetectPlatform(); - Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; + var context = new InstallerContext(); + Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}"; Console.WriteLine(); /**** ** Check if correct installer ****/ #if SMAPI_FOR_WINDOWS - if (platform == Platform.Linux || platform == Platform.Mac) + if (context.IsUnix) { - this.PrintError($"This is the installer for Windows. Run the 'install on {platform}.{(platform == Platform.Linux ? "sh" : "command")}' file instead."); + this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead."); Console.ReadLine(); return; } #else - if (platform == Platform.Windows) + if (context.IsWindows) { this.PrintError($"This is the installer for Linux/Mac. Run the 'install on Windows.exe' file instead."); Console.ReadLine(); @@ -155,20 +151,20 @@ namespace StardewModdingApi.Installer /**** ** Check Windows dependencies ****/ - if (platform == Platform.Windows) + if (context.IsWindows) { // .NET Framework 4.5+ - if (!this.HasNetFramework45(platform)) + if (!context.HasNetFramework45()) { - this.PrintError(Environment.OSVersion.Version >= this.Windows7Version - ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ - : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier + this.PrintError(context.CanInstallLatestNetFramework() + ? "Please install the latest version of .NET Framework before installing SMAPI." + : "Please install .NET Framework 4.5 before installing SMAPI." ); this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); Console.ReadLine(); return; } - if (!this.HasXna(platform)) + if (!context.HasXna()) { this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); Console.ReadLine(); @@ -202,7 +198,7 @@ namespace StardewModdingApi.Installer ** Step 2: choose a theme (can't auto-detect on Linux/Mac) *********/ MonitorColorScheme scheme = MonitorColorScheme.AutoDetect; - if (platform == Platform.Linux || platform == Platform.Mac) + if (context.IsUnix) { /**** ** print header @@ -215,8 +211,8 @@ namespace StardewModdingApi.Installer ** show theme selector ****/ // get theme writers - var lightBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground)); - var darkBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground)); + var lightBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground)); + var darkBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground)); // print question this.PrintPlain("Which text looks more readable?"); @@ -264,7 +260,7 @@ namespace StardewModdingApi.Installer ****/ // get game path this.PrintInfo("Where is your game folder?"); - DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, toolkit, gamePathArg); + DirectoryInfo installDir = this.InteractivelyGetInstallPath(toolkit, context, gamePathArg); if (installDir == null) { this.PrintError("Failed finding your game path."); @@ -274,7 +270,7 @@ namespace StardewModdingApi.Installer // get folders DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath); - paths = new InstallerPaths(bundleDir, installDir, EnvironmentUtility.GetExecutableName(platform)); + paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName); } Console.Clear(); @@ -359,7 +355,7 @@ namespace StardewModdingApi.Installer ** Always uninstall old files ****/ // restore game launcher - if (platform.IsMono() && File.Exists(paths.UnixBackupLauncherPath)) + if (context.IsUnix && File.Exists(paths.UnixBackupLauncherPath)) { this.PrintDebug("Removing SMAPI launcher..."); this.InteractivelyDelete(paths.UnixLauncherPath); @@ -406,7 +402,7 @@ namespace StardewModdingApi.Installer } // replace mod launcher (if possible) - if (platform.IsMono()) + if (context.IsUnix) { this.PrintDebug("Safely replacing game launcher..."); @@ -504,7 +500,7 @@ namespace StardewModdingApi.Installer /********* ** Step 7: final instructions *********/ - if (platform == Platform.Windows) + if (context.IsWindows) { if (action == ScriptAction.Install) { @@ -531,16 +527,6 @@ namespace StardewModdingApi.Installer /********* ** Private methods *********/ - /// <summary>Get the display text for an assembly version.</summary> - /// <param name="version">The assembly version.</param> - private string GetDisplayVersion(Version version) - { - string str = $"{version.Major}.{version.Minor}"; - if (version.Build != 0) - str += $".{version.Build}"; - return str; - } - /// <summary>Get the display text for a color scheme.</summary> /// <param name="scheme">The color scheme.</param> private string GetDisplayText(MonitorColorScheme scheme) @@ -582,38 +568,6 @@ namespace StardewModdingApi.Installer /// <param name="text">The text to print.</param> private void PrintSuccess(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success); - /// <summary>Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows.</summary> - /// <param name="platform">The current platform.</param> - /// <exception cref="NotSupportedException">The current platform is not Windows.</exception> - private bool HasNetFramework45(Platform platform) - { - switch (platform) - { - case Platform.Windows: - using (RegistryKey versionKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full")) - return versionKey?.GetValue("Release") != null; // .NET Framework 4.5+ - - default: - throw new NotSupportedException("The installed .NET Framework version can only be checked on Windows."); - } - } - - /// <summary>Get whether the current system has XNA Framework installed. This only applies on Windows.</summary> - /// <param name="platform">The current platform.</param> - /// <exception cref="NotSupportedException">The current platform is not Windows.</exception> - private bool HasXna(Platform platform) - { - switch (platform) - { - case Platform.Windows: - using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\XNA\Framework")) - return key != null; // XNA Framework 4.0+ - - default: - throw new NotSupportedException("The installed XNA Framework version can only be checked on Windows."); - } - } - /// <summary>Interactively delete a file or folder path, and block until deletion completes.</summary> /// <param name="path">The file or folder path.</param> private void InteractivelyDelete(string path) @@ -687,15 +641,12 @@ namespace StardewModdingApi.Installer } /// <summary>Interactively locate the game install path to update.</summary> - /// <param name="platform">The current platform.</param> /// <param name="toolkit">The mod toolkit.</param> + /// <param name="context">The installer context.</param> /// <param name="specifiedPath">The path specified as a command-line argument (if any), which should override automatic path detection.</param> - private DirectoryInfo InteractivelyGetInstallPath(Platform platform, ModToolkit toolkit, string specifiedPath) + private DirectoryInfo InteractivelyGetInstallPath(ModToolkit toolkit, InstallerContext context, string specifiedPath) { - // get executable name - string executableFilename = EnvironmentUtility.GetExecutableName(platform); - - // validate specified path + // use specified path if (specifiedPath != null) { var dir = new DirectoryInfo(specifiedPath); @@ -704,7 +655,7 @@ namespace StardewModdingApi.Installer this.PrintError($"You specified --game-path \"{specifiedPath}\", but that folder doesn't exist."); return null; } - if (!dir.EnumerateFiles(executableFilename).Any()) + if (!context.LooksLikeGameFolder(dir)) { this.PrintError($"You specified --game-path \"{specifiedPath}\", but that folder doesn't contain the Stardew Valley executable."); return null; @@ -712,7 +663,19 @@ namespace StardewModdingApi.Installer return dir; } - // get installed paths + // use game folder which contains the installer, if any + { + DirectoryInfo curPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + while (curPath?.Parent != null) // must be in a folder (not at the root) + { + if (context.LooksLikeGameFolder(curPath)) + return curPath; + + curPath = curPath.Parent; + } + } + + // use an installed path DirectoryInfo[] defaultPaths = toolkit.GetGameFolders().ToArray(); if (defaultPaths.Any()) { @@ -738,7 +701,7 @@ namespace StardewModdingApi.Installer while (true) { // get path from user - this.PrintInfo($"Type the file path to the game directory (the one containing '{executableFilename}'), then press enter."); + this.PrintInfo($"Type the file path to the game directory (the one containing '{context.ExecutableName}'), then press enter."); string path = Console.ReadLine()?.Trim(); if (string.IsNullOrWhiteSpace(path)) { @@ -747,10 +710,9 @@ namespace StardewModdingApi.Installer } // normalize path - if (platform == Platform.Windows) - path = path.Replace("\"", ""); // in Windows, quotes are used to escape spaces and aren't part of the file path - if (platform == Platform.Linux || platform == Platform.Mac) - path = path.Replace("\\ ", " "); // in Linux/Mac, spaces in paths may be escaped if copied from the command line + path = context.IsWindows + ? path.Replace("\"", "") // in Windows, quotes are used to escape spaces and aren't part of the file path + : path.Replace("\\ ", " "); // in Linux/Mac, spaces in paths may be escaped if copied from the command line if (path.StartsWith("~/")) { string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE"); @@ -768,7 +730,7 @@ namespace StardewModdingApi.Installer this.PrintInfo(" That directory doesn't seem to exist."); continue; } - if (!directory.EnumerateFiles(executableFilename).Any()) + if (!context.LooksLikeGameFolder(directory)) { this.PrintInfo(" That directory doesn't contain a Stardew Valley executable."); continue; diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 825988a5..d4c82180 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -15,20 +15,33 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning public class GameScanner { /********* + ** Fields + *********/ + /// <summary>The current OS.</summary> + private readonly Platform Platform; + + /// <summary>The name of the Stardew Valley executable.</summary> + private readonly string ExecutableName; + + + /********* ** Public methods *********/ + /// <summary>Construct an instance.</summary> + public GameScanner() + { + this.Platform = EnvironmentUtility.DetectPlatform(); + this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform); + } + /// <summary>Find all valid Stardew Valley install folders.</summary> /// <remarks>This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS.</remarks> public IEnumerable<DirectoryInfo> Scan() { - // get OS info - Platform platform = EnvironmentUtility.DetectPlatform(); - string executableFilename = EnvironmentUtility.GetExecutableName(platform); - // get install paths IEnumerable<string> paths = this - .GetCustomInstallPaths(platform) - .Concat(this.GetDefaultInstallPaths(platform)) + .GetCustomInstallPaths() + .Concat(this.GetDefaultInstallPaths()) .Select(PathUtilities.NormalizePath) .Distinct(StringComparer.OrdinalIgnoreCase); @@ -36,21 +49,27 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning foreach (string path in paths) { DirectoryInfo folder = new DirectoryInfo(path); - if (folder.Exists && folder.EnumerateFiles(executableFilename).Any()) + if (this.LooksLikeGameFolder(folder)) yield return folder; } } + /// <summary>Get whether a folder seems to contain the game.</summary> + /// <param name="dir">The folder to check.</param> + public bool LooksLikeGameFolder(DirectoryInfo dir) + { + return dir.Exists && dir.EnumerateFiles(this.ExecutableName).Any(); + } + /********* ** Private methods *********/ /// <summary>The default file paths where Stardew Valley can be installed.</summary> - /// <param name="platform">The target platform.</param> - /// <remarks>Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. </remarks> - private IEnumerable<string> GetDefaultInstallPaths(Platform platform) + /// <remarks>Derived from the <a href="https://github.com/Pathoschild/Stardew.ModBuildConfig">crossplatform mod config</a>.</remarks> + private IEnumerable<string> GetDefaultInstallPaths() { - switch (platform) + switch (this.Platform) { case Platform.Linux: case Platform.Mac: @@ -102,16 +121,15 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning break; default: - throw new InvalidOperationException($"Unknown platform '{platform}'."); + throw new InvalidOperationException($"Unknown platform '{this.Platform}'."); } } /// <summary>Get the custom install path from the <c>stardewvalley.targets</c> file in the home directory, if any.</summary> - /// <param name="platform">The target platform.</param> - private IEnumerable<string> GetCustomInstallPaths(Platform platform) + private IEnumerable<string> GetCustomInstallPaths() { // get home path - string homePath = Environment.GetEnvironmentVariable(platform == Platform.Windows ? "USERPROFILE" : "HOME"); + string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME"); if (string.IsNullOrWhiteSpace(homePath)) yield break; |