diff options
Diffstat (limited to 'src/SMAPI.Installer')
-rw-r--r-- | src/SMAPI.Installer/Framework/InstallerContext.cs | 4 | ||||
-rw-r--r-- | src/SMAPI.Installer/InteractiveInstaller.cs | 101 | ||||
-rw-r--r-- | src/SMAPI.Installer/Program.cs | 12 | ||||
-rw-r--r-- | src/SMAPI.Installer/assets/unix-launcher.sh | 53 |
4 files changed, 106 insertions, 64 deletions
diff --git a/src/SMAPI.Installer/Framework/InstallerContext.cs b/src/SMAPI.Installer/Framework/InstallerContext.cs index bb973230..a2c63dd8 100644 --- a/src/SMAPI.Installer/Framework/InstallerContext.cs +++ b/src/SMAPI.Installer/Framework/InstallerContext.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI.Installer.Framework ** Fields *********/ /// <summary>The underlying toolkit game scanner.</summary> - private readonly GameScanner GameScanner = new GameScanner(); + private readonly GameScanner GameScanner = new(); /********* @@ -44,7 +44,7 @@ namespace StardewModdingAPI.Installer.Framework /// <summary>Get the installer's version number.</summary> public ISemanticVersion GetInstallerVersion() { - var raw = this.GetType().Assembly.GetName().Version; + var raw = this.GetType().Assembly.GetName().Version!; return new SemanticVersion(raw); } diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index b3bba883..5138173a 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -35,6 +36,7 @@ namespace StardewModdingApi.Installer /// <summary>Get the absolute file or folder paths to remove when uninstalling SMAPI.</summary> /// <param name="installDir">The folder for Stardew Valley and SMAPI.</param> /// <param name="modsDir">The folder for SMAPI mods.</param> + [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These are valid file names.")] private IEnumerable<string> GetUninstallPaths(DirectoryInfo installDir, DirectoryInfo modsDir) { string GetInstallPath(string path) => Path.Combine(installDir.FullName, path); @@ -126,7 +128,7 @@ namespace StardewModdingApi.Installer /**** ** Get basic info & set window title ****/ - ModToolkit toolkit = new ModToolkit(); + ModToolkit toolkit = new(); var context = new InstallerContext(); Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}"; Console.WriteLine(); @@ -164,7 +166,7 @@ namespace StardewModdingApi.Installer } // get game path from CLI - string gamePathArg = null; + string? gamePathArg = null; { int pathIndex = Array.LastIndexOf(args, "--game-path") + 1; if (pathIndex >= 1 && args.Length >= pathIndex) @@ -189,8 +191,8 @@ namespace StardewModdingApi.Installer ** show theme selector ****/ // get theme writers - var lightBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground)); - var darkBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground)); + ColorfulConsoleWriter lightBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground)); + ColorfulConsoleWriter darkBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground)); // print question this.PrintPlain("Which text looks more readable?"); @@ -237,7 +239,7 @@ namespace StardewModdingApi.Installer ** collect details ****/ // get game path - DirectoryInfo installDir = this.InteractivelyGetInstallPath(toolkit, context, gamePathArg); + DirectoryInfo? installDir = this.InteractivelyGetInstallPath(toolkit, context, gamePathArg); if (installDir == null) { this.PrintError("Failed finding your game path."); @@ -246,7 +248,7 @@ namespace StardewModdingApi.Installer } // get folders - DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath); + DirectoryInfo bundleDir = new(this.BundlePath); paths = new InstallerPaths(bundleDir, installDir); } @@ -354,8 +356,8 @@ namespace StardewModdingApi.Installer // move global save data folder (changed in 3.2) { string dataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); - DirectoryInfo oldDir = new DirectoryInfo(Path.Combine(dataPath, "Saves", ".smapi")); - DirectoryInfo newDir = new DirectoryInfo(Path.Combine(dataPath, ".smapi")); + DirectoryInfo oldDir = new(Path.Combine(dataPath, "Saves", ".smapi")); + DirectoryInfo newDir = new(Path.Combine(dataPath, ".smapi")); if (oldDir.Exists) { @@ -428,7 +430,7 @@ namespace StardewModdingApi.Installer } // add or replace bundled mods - DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods")); + DirectoryInfo bundledModsDir = new(Path.Combine(paths.BundlePath, "Mods")); if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any()) { this.PrintDebug("Adding bundled mods..."); @@ -449,8 +451,8 @@ namespace StardewModdingApi.Installer } // find target folder - ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true); - DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); + ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true); + DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName ? $" adding {sourceMod.Manifest.Name}..." @@ -532,27 +534,45 @@ namespace StardewModdingApi.Installer /// <summary>Print a message without formatting.</summary> /// <param name="text">The text to print.</param> - private void PrintPlain(string text) => Console.WriteLine(text); + private void PrintPlain(string text) + { + Console.WriteLine(text); + } /// <summary>Print a debug message.</summary> /// <param name="text">The text to print.</param> - private void PrintDebug(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Debug); + private void PrintDebug(string text) + { + this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Debug); + } /// <summary>Print a debug message.</summary> /// <param name="text">The text to print.</param> - private void PrintInfo(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Info); + private void PrintInfo(string text) + { + this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Info); + } /// <summary>Print a warning message.</summary> /// <param name="text">The text to print.</param> - private void PrintWarning(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Warn); + private void PrintWarning(string text) + { + this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Warn); + } /// <summary>Print a warning message.</summary> /// <param name="text">The text to print.</param> - private void PrintError(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Error); + private void PrintError(string text) + { + this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Error); + } /// <summary>Print a success message.</summary> /// <param name="text">The text to print.</param> - private void PrintSuccess(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success); + private void PrintSuccess(string text) + { + this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success); + } /// <summary>Interactively delete a file or folder path, and block until deletion completes.</summary> /// <param name="path">The file or folder path.</param> @@ -562,7 +582,7 @@ namespace StardewModdingApi.Installer { try { - FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : (FileSystemInfo)new FileInfo(path)); + FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : new FileInfo(path)); break; } catch (Exception ex) @@ -578,7 +598,7 @@ namespace StardewModdingApi.Installer /// <param name="source">The file or folder to copy.</param> /// <param name="targetFolder">The folder to copy into.</param> /// <param name="filter">A filter which matches directories and files to copy, or <c>null</c> to match all.</param> - private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter = null) + private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool>? filter = null) { if (filter != null && !filter(source)) return; @@ -593,8 +613,8 @@ namespace StardewModdingApi.Installer break; case DirectoryInfo sourceDir: - DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)); - foreach (var entry in sourceDir.EnumerateFileSystemInfos()) + DirectoryInfo targetSubfolder = new(Path.Combine(targetFolder.FullName, sourceDir.Name)); + foreach (FileSystemInfo entry in sourceDir.EnumerateFileSystemInfos()) this.RecursiveCopy(entry, targetSubfolder, filter); break; @@ -608,7 +628,7 @@ namespace StardewModdingApi.Installer /// <param name="message">The message to print.</param> /// <param name="options">The allowed options (not case sensitive).</param> /// <param name="indent">The indentation to prefix to output.</param> - private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string> print = null) + private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? print = null) { print ??= this.PrintInfo; @@ -616,8 +636,8 @@ namespace StardewModdingApi.Installer { print(indent + message); Console.Write(indent); - string input = Console.ReadLine()?.Trim().ToLowerInvariant(); - if (!options.Contains(input)) + string? input = Console.ReadLine()?.Trim().ToLowerInvariant(); + if (input == null || !options.Contains(input)) { print($"{indent}That's not a valid option."); continue; @@ -630,7 +650,7 @@ namespace StardewModdingApi.Installer /// <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(ModToolkit toolkit, InstallerContext context, string specifiedPath) + private DirectoryInfo? InteractivelyGetInstallPath(ModToolkit toolkit, InstallerContext context, string? specifiedPath) { // use specified path if (specifiedPath != null) @@ -697,7 +717,7 @@ namespace StardewModdingApi.Installer // get path from user Console.WriteLine(); this.PrintInfo($"Type the file path to the game directory (the one containing '{Constants.GameDllName}'), then press enter."); - string path = Console.ReadLine()?.Trim(); + string? path = Console.ReadLine()?.Trim(); if (string.IsNullOrWhiteSpace(path)) { this.PrintWarning("You must specify a directory path to continue."); @@ -710,14 +730,14 @@ namespace StardewModdingApi.Installer : path.Replace("\\ ", " "); // in Linux/macOS, spaces in paths may be escaped if copied from the command line if (path.StartsWith("~/")) { - string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE"); + string home = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetEnvironmentVariable("USERPROFILE")!; path = Path.Combine(home, path.Substring(2)); } // get directory if (File.Exists(path)) - path = Path.GetDirectoryName(path); - DirectoryInfo directory = new DirectoryInfo(path); + path = Path.GetDirectoryName(path)!; + DirectoryInfo directory = new(path); // validate path if (!directory.Exists) @@ -763,7 +783,7 @@ namespace StardewModdingApi.Installer // game folder which contains the installer, if any { - DirectoryInfo curPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + 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)) @@ -785,7 +805,7 @@ namespace StardewModdingApi.Installer } } - /// <summary>Interactively move mods out of the appdata directory.</summary> + /// <summary>Interactively move mods out of the app data directory.</summary> /// <param name="properModsDir">The directory which should contain all mods.</param> /// <param name="packagedModsDir">The installer directory containing packaged mods.</param> private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir) @@ -795,7 +815,7 @@ namespace StardewModdingApi.Installer // get path string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); - DirectoryInfo modDir = new DirectoryInfo(Path.Combine(appDataPath, "Mods")); + DirectoryInfo modDir = new(Path.Combine(appDataPath, "Mods")); // check if migration needed if (!modDir.Exists) @@ -808,7 +828,7 @@ namespace StardewModdingApi.Installer { // get type bool isDir = entry is DirectoryInfo; - if (!isDir && !(entry is FileInfo)) + if (!isDir && entry is not FileInfo) continue; // should never happen // delete packaged mods (newer version bundled into SMAPI) @@ -845,7 +865,7 @@ namespace StardewModdingApi.Installer /// <summary>Move a filesystem entry to a new parent directory.</summary> /// <param name="entry">The filesystem entry to move.</param> /// <param name="newPath">The destination path.</param> - /// <remarks>We can't use <see cref="FileInfo.MoveTo"/> or <see cref="DirectoryInfo.MoveTo"/>, because those don't work across partitions.</remarks> + /// <remarks>We can't use <see cref="FileInfo.MoveTo(string)"/> or <see cref="DirectoryInfo.MoveTo"/>, because those don't work across partitions.</remarks> private void Move(FileSystemInfo entry, string newPath) { // file @@ -872,15 +892,12 @@ namespace StardewModdingApi.Installer /// <param name="entry">The file or folder info.</param> private bool ShouldCopy(FileSystemInfo entry) { - switch (entry.Name) + return entry.Name switch { - case "mcs": - return false; // ignore macOS symlink - case "Mods": - return false; // Mods folder handled separately - default: - return true; - } + "mcs" => false, // ignore macOS symlink + "Mods" => false, // Mods folder handled separately + _ => true + }; } } } diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs index 45cfea75..dc452a46 100644 --- a/src/SMAPI.Installer/Program.cs +++ b/src/SMAPI.Installer/Program.cs @@ -15,7 +15,7 @@ namespace StardewModdingApi.Installer *********/ /// <summary>The absolute path of the installer folder.</summary> [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")] - private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; /// <summary>The absolute path of the folder containing the unzipped installer files.</summary> private static readonly string ExtractedBundlePath = Path.Combine(Path.GetTempPath(), $"SMAPI-installer-{Guid.NewGuid():N}"); @@ -31,7 +31,7 @@ namespace StardewModdingApi.Installer public static void Main(string[] args) { // find install bundle - FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, "install.dat")); + FileInfo zipFile = new(Path.Combine(Program.InstallerPath, "install.dat")); if (!zipFile.Exists) { Console.WriteLine($"Oops! Some of the installer files are missing; try re-downloading the installer. (Missing file: {zipFile.FullName})"); @@ -40,7 +40,7 @@ namespace StardewModdingApi.Installer } // unzip bundle into temp folder - DirectoryInfo bundleDir = new DirectoryInfo(Program.ExtractedBundlePath); + DirectoryInfo bundleDir = new(Program.ExtractedBundlePath); Console.WriteLine("Extracting install files..."); ZipFile.ExtractToDirectory(zipFile.FullName, bundleDir.FullName); @@ -66,14 +66,14 @@ namespace StardewModdingApi.Installer /// <summary>Method called when assembly resolution fails, which may return a manually resolved assembly.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event arguments.</param> - private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) + private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs e) { try { - AssemblyName name = new AssemblyName(e.Name); + AssemblyName name = new(e.Name); foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll")) { - if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) + if (name.Name != null && name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) return Assembly.LoadFrom(dll.FullName); } return null; diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh index 47937f95..ae9624e7 100644 --- a/src/SMAPI.Installer/assets/unix-launcher.sh +++ b/src/SMAPI.Installer/assets/unix-launcher.sh @@ -6,10 +6,41 @@ # move to script's directory cd "$(dirname "$0")" || exit $? -# change to true to skip opening a terminal +# Whether to avoid opening a separate terminal window, and avoid logging anything to the console. # This isn't recommended since you won't see errors, warnings, and update alerts. SKIP_TERMINAL=false +# Whether to avoid opening a separate terminal, but still send the usual log output to the console. +USE_CURRENT_SHELL=false + + +########## +## Read environment variables +########## +if [ "$SMAPI_NO_TERMINAL" == "true" ]; then + SKIP_TERMINAL=true +fi +if [ "$SMAPI_USE_CURRENT_SHELL" == "true" ]; then + USE_CURRENT_SHELL=true +fi + + +########## +## Read command-line arguments +########## +while [ "$#" -gt 0 ]; do + case "$1" in + --skip-terminal ) SKIP_TERMINAL=true; shift ;; + --use-current-shell ) USE_CURRENT_SHELL=true; shift ;; + -- ) shift; break ;; + * ) shift ;; + esac +done + +if [ "$SKIP_TERMINAL" == "true" ]; then + USE_CURRENT_SHELL=true +fi + ########## ## Open terminal if needed @@ -18,21 +49,13 @@ SKIP_TERMINAL=false # Besides letting the player see errors/warnings/alerts in the console, this is also needed because # Steam messes with the PATH. if [ "$(uname)" == "Darwin" ]; then - if [ ! -t 1 ]; then # https://stackoverflow.com/q/911168/262123 - # sanity check to make sure we don't have an infinite loop of opening windows - for argument in "$@"; do - if [ "$argument" == "--no-reopen-terminal" ]; then - SKIP_TERMINAL=true - break - fi - done - + if [ ! -t 1 ]; then # not open in Terminal (https://stackoverflow.com/q/911168/262123) # reopen in Terminal if needed # https://stackoverflow.com/a/29511052/262123 - if [ "$SKIP_TERMINAL" == "false" ]; then + if [ "$USE_CURRENT_SHELL" == "false" ]; then echo "Reopening in the Terminal app..." echo '#!/bin/sh' > /tmp/open-smapi-terminal.sh - echo "\"$0\" $@ --no-reopen-terminal" >> /tmp/open-smapi-terminal.sh + echo "\"$0\" $@ --use-current-shell" >> /tmp/open-smapi-terminal.sh chmod +x /tmp/open-smapi-terminal.sh cat /tmp/open-smapi-terminal.sh open -W -a Terminal /tmp/open-smapi-terminal.sh @@ -68,7 +91,7 @@ else export LAUNCH_FILE # run in terminal - if [ "$SKIP_TERMINAL" == "false" ]; then + if [ "$USE_CURRENT_SHELL" == "false" ]; then # select terminal (prefer xterm for best compatibility, then known supported terminals) for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do if command -v "$terminal" 2>/dev/null; then @@ -131,7 +154,9 @@ else fi # explicitly run without terminal - else + elif [ "$SKIP_TERMINAL" == "true" ]; then exec $LAUNCH_FILE --no-terminal "$@" + else + exec $LAUNCH_FILE "$@" fi fi |