summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Installer/Framework/InstallerContext.cs103
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs124
-rw-r--r--src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs48
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;