From c627348c25774600e83248182336bdc857feda0a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 23 Nov 2020 18:20:52 -0500 Subject: let players specify game path by running the installer from within it --- src/SMAPI.Installer/Framework/InstallerContext.cs | 103 +++++++++++++++++ src/SMAPI.Installer/InteractiveInstaller.cs | 124 +++++++-------------- .../Framework/GameScanning/GameScanner.cs | 48 +++++--- 3 files changed, 179 insertions(+), 96 deletions(-) create mode 100644 src/SMAPI.Installer/Framework/InstallerContext.cs (limited to 'src') 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 +{ + /// The installer context. + internal class InstallerContext + { + /********* + ** Fields + *********/ + /// The value that represents Windows 7. + private readonly Version Windows7Version = new Version(6, 1); + + /// The underlying toolkit game scanner. + private readonly GameScanner GameScanner = new GameScanner(); + + + /********* + ** Accessors + *********/ + /// The current OS. + public Platform Platform { get; } + + /// The human-readable OS name and version. + public string PlatformName { get; } + + /// The name of the Stardew Valley executable. + public string ExecutableName { get; } + + /// Whether the installer is running on Windows. + public bool IsWindows => this.Platform == Platform.Windows; + + /// Whether the installer is running on a Unix OS (including Linux or MacOS). + public bool IsUnix => !this.IsWindows; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public InstallerContext() + { + this.Platform = EnvironmentUtility.DetectPlatform(); + this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform); + this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform); + } + + /// Get the installer's version number. + public ISemanticVersion GetInstallerVersion() + { + var raw = this.GetType().Assembly.GetName().Version; + return new SemanticVersion(raw); + } + + /// Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows. + /// The current platform is not Windows. + 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."); + } + } + + /// Get whether the current system has XNA Framework installed. This only applies on Windows. + /// The current platform is not Windows. + 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."); + } + } + + /// Whether the current OS supports newer versions of .NET Framework. + public bool CanInstallLatestNetFramework() + { + return Environment.OSVersion.Version >= this.Windows7Version; // Windows 7+ + } + + /// Get whether a folder seems to contain the game files. + /// The folder to check. + 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 /// The absolute path to the directory containing the files to copy into the game folder. private readonly string BundlePath; - /// The value that represents Windows 7. - private readonly Version Windows7Version = new Version(6, 1); - /// The mod IDs which the installer should allow as bundled mods. - 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 *********/ - /// Get the display text for an assembly version. - /// The assembly version. - private string GetDisplayVersion(Version version) - { - string str = $"{version.Major}.{version.Minor}"; - if (version.Build != 0) - str += $".{version.Build}"; - return str; - } - /// Get the display text for a color scheme. /// The color scheme. private string GetDisplayText(MonitorColorScheme scheme) @@ -582,38 +568,6 @@ namespace StardewModdingApi.Installer /// The text to print. private void PrintSuccess(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Success); - /// Get whether the current system has .NET Framework 4.5 or later installed. This only applies on Windows. - /// The current platform. - /// The current platform is not Windows. - 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."); - } - } - - /// Get whether the current system has XNA Framework installed. This only applies on Windows. - /// The current platform. - /// The current platform is not Windows. - 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."); - } - } - /// Interactively delete a file or folder path, and block until deletion completes. /// The file or folder path. private void InteractivelyDelete(string path) @@ -687,15 +641,12 @@ namespace StardewModdingApi.Installer } /// Interactively locate the game install path to update. - /// The current platform. /// The mod toolkit. + /// The installer context. /// The path specified as a command-line argument (if any), which should override automatic path detection. - 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 @@ -14,21 +14,34 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning /// Finds installed game folders. public class GameScanner { + /********* + ** Fields + *********/ + /// The current OS. + private readonly Platform Platform; + + /// The name of the Stardew Valley executable. + private readonly string ExecutableName; + + /********* ** Public methods *********/ + /// Construct an instance. + public GameScanner() + { + this.Platform = EnvironmentUtility.DetectPlatform(); + this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform); + } + /// Find all valid Stardew Valley install folders. /// 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. public IEnumerable Scan() { - // get OS info - Platform platform = EnvironmentUtility.DetectPlatform(); - string executableFilename = EnvironmentUtility.GetExecutableName(platform); - // get install paths IEnumerable 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; } } + /// Get whether a folder seems to contain the game. + /// The folder to check. + public bool LooksLikeGameFolder(DirectoryInfo dir) + { + return dir.Exists && dir.EnumerateFiles(this.ExecutableName).Any(); + } + /********* ** Private methods *********/ /// The default file paths where Stardew Valley can be installed. - /// The target platform. - /// Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. - private IEnumerable GetDefaultInstallPaths(Platform platform) + /// Derived from the crossplatform mod config. + private IEnumerable 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}'."); } } /// Get the custom install path from the stardewvalley.targets file in the home directory, if any. - /// The target platform. - private IEnumerable GetCustomInstallPaths(Platform platform) + private IEnumerable 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; -- cgit From 1e2000126d32f883b820538ba6873a5d5e5f0d19 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 5 Dec 2020 12:49:06 -0500 Subject: update schema for Content Patcher 1.19 --- src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 37 +++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index e52cd757..92149f4d 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -11,9 +11,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.", "type": "string", - "const": "1.18.0", + "const": "1.19.0", "@errorMessages": { - "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.18.0'." + "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.19.0'." } }, "ConfigSchema": { @@ -147,13 +147,16 @@ }, "Update": { "title": "Update", - "description": "When the patch should update if it changed. The possible values are 'OnDayStart' and 'OnLocationChange' (defaults to OnDayStart).", + "description": "When the patch should update if it changed. The possible values are 'OnDayStart', 'OnLocationChange', or 'OnTimeChange' (defaults to OnDayStart).", "type": "string", - "enum": [ "OnDayStart", "OnLocationChange" ] + "pattern": "^ *((OnDayStart|OnLocationChange|OnTimeChange), *)*(OnDayStart|OnLocationChange|OnTimeChange) *$", + "@errorMessages": { + "pattern": "Invalid value; must be 'OnDayStart', 'OnLocationChange', 'OnTimeChange', or a comma-delimited combination of those values." + } }, "FromFile": { "title": "Source file", - "description": "The relative file path in your content pack folder to load instead (like 'assets/dinosaur.png'). This can be a .json (data), .png (image), .tbin or .tmx (map), or .xnb file. This field supports tokens and capitalization doesn't matter.", + "description": "The relative file path in your content pack folder to load instead (like 'assets/dinosaur.png'), or multiple comma-delimited values. This can be a .json (data), .png (image), .tbin or .tmx (map), or .xnb file. This field supports tokens and capitalization doesn't matter.", "type": "string", "allOf": [ { @@ -180,13 +183,6 @@ "description": "The part of the target image to replace. Defaults to the FromArea size starting from the top-left corner.", "$ref": "#/definitions/Rectangle" }, - "PatchMode": { - "title": "Patch mode", - "description": "How to apply FromArea to ToArea. Defaults to Replace.", - "type": "string", - "enum": [ "Replace", "Overlay" ], - "default": "Replace" - }, "Fields": { "title": "Fields", "description": "The individual fields you want to change for existing entries. This field supports tokens in field keys and values. The key for each field is the field index (starting at zero) for a slash-delimited string, or the field name for an object.", @@ -359,6 +355,15 @@ } }, "then": { + "properties": { + "PatchMode": { + "title": "Patch mode", + "description": "How to apply FromArea to ToArea. Defaults to Replace.", + "type": "string", + "enum": [ "Replace", "Overlay" ], + "default": "Replace" + } + }, "required": [ "FromFile", "Target" ], "propertyNames": { "enum": [ @@ -417,6 +422,13 @@ }, "ToArea": { "description": "The part of the target map to replace." + }, + "PatchMode": { + "title": "Patch mode", + "description": "How to apply FromArea to ToArea. Defaults to ReplaceByLayer.", + "type": "string", + "enum": [ "Overlay", "Replace", "ReplaceByLayer" ], + "default": "ReplaceByLayer" } }, "propertyNames": { @@ -432,6 +444,7 @@ "FromArea", "MapProperties", "MapTiles", + "PatchMode", "TextOperations", "ToArea" ] -- cgit From 1c70736c00e6e70f46f539cb26b5fd253f4eff3b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 8 Dec 2020 08:23:16 -0500 Subject: clarify not-a-mod error when SMAPI installer is in mods folder --- docs/release-notes.md | 1 + src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 8043212e..0bbbeb58 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * When the installer is run from within a game folder, it now installs SMAPI to that folder. That simplifies installation if you have multiple copies of the game or it can't otherwise auto-detect the game path. + * Clarified not-a-mod error when the SMAPI installer is in the `Mods` folder. ## 3.7.6 Released 21 November 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 6d6b6417..5eacee9e 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -112,10 +112,20 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning if (manifestFile == null) { FileInfo[] files = this.RecursivelyGetRelevantFiles(searchFolder).ToArray(); + + // empty folder if (!files.Any()) return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyFolder, "it's an empty folder."); + + // XNB mod if (files.All(this.IsPotentialXnbFile)) return new ModFolder(root, searchFolder, ModType.Xnb, null, ModParseError.XnbMod, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); + + // SMAPI installer + if (files.Any(p => p.Name == "install on Linux.sh" || p.Name == "install on Mac.command" || p.Name == "install on Windows.bat")) + return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.ManifestMissing, "the SMAPI installer isn't a mod (you can delete this folder after running the installer file)."); + + // not a mod? return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.ManifestMissing, "it contains files, but none of them are manifest.json."); } -- cgit From 50a146d1c9a228391c4201685a5e0df9daa529e9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 20 Dec 2020 22:34:58 -0500 Subject: update game version --- docs/release-notes.md | 4 ++++ src/SMAPI/Constants.cs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 1115f482..00445832 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> +## Upcoming release +* For players: + * Updated for Stardew Valley 1.5. + ## 3.7.6 Released 21 November 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 88f79811..5ff324b0 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -54,10 +54,10 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.7.6"); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); + public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.0"); /// The maximum supported version of Stardew Valley. - public static ISemanticVersion MaximumGameVersion { get; } = new GameVersion("1.4.5"); + public static ISemanticVersion MaximumGameVersion { get; } = null; /// The target game platform. public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform; -- cgit From 2e8c7e06c5c46834b570b667cb7497fe4cc408ac Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 20 Dec 2020 22:34:59 -0500 Subject: update for split-screen mode This includes splitting GameRunner (the main game instance) from Game1 (now a per-screen game state), adding a PerScreen utility to simplify per-screen values, adding separate per-screen input handling and events, adding new Context fields for split-screen, and logging the screen ID in split-screen mode to distinguish log entries. --- docs/release-notes.md | 6 +- .../Framework/LogParsing/LogMessageBuilder.cs | 8 +- src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 4 +- .../Framework/LogParsing/Models/LogMessage.cs | 3 + src/SMAPI.Web/Views/LogParser/Index.cshtml | 11 +- src/SMAPI/Constants.cs | 3 + src/SMAPI/Context.cs | 79 ++++- src/SMAPI/Framework/Logging/LogManager.cs | 5 +- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 8 +- src/SMAPI/Framework/ModHelpers/InputHelper.cs | 21 +- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 6 +- src/SMAPI/Framework/Monitor.cs | 11 +- src/SMAPI/Framework/SCore.cs | 358 +++++++++++---------- src/SMAPI/Framework/SGame.cs | 121 ++++--- src/SMAPI/Framework/SGameRunner.cs | 156 +++++++++ src/SMAPI/Utilities/PerScreen.cs | 79 +++++ 16 files changed, 624 insertions(+), 255 deletions(-) create mode 100644 src/SMAPI/Framework/SGameRunner.cs create mode 100644 src/SMAPI/Utilities/PerScreen.cs (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 00445832..176961d4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,7 +9,11 @@ ## Upcoming release * For players: - * Updated for Stardew Valley 1.5. + * Updated for Stardew Valley 1.5, including split-screen support. + +* For modders: + * Added `PerScreen` utility and new `Context` fields to simplify split-screen support in mods. + * Added screen ID to log when playing in split-screen mode. ## 3.7.6 Released 21 November 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs index 42e283a9..992876ef 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs @@ -16,6 +16,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing /// The log level for the next log message. public LogLevel Level { get; set; } + /// The screen ID in split-screen mode. + public int ScreenId { get; set; } + /// The mod name for the next log message. public string Mod { get; set; } @@ -36,10 +39,11 @@ namespace StardewModdingAPI.Web.Framework.LogParsing /// Start accumulating values for a new log message. /// The local time when the log was posted. /// The log level. + /// The screen ID in split-screen mode. /// The mod name. /// The initial log text. /// A log message is already started; call before starting a new message. - public void Start(string time, LogLevel level, string mod, string text) + public void Start(string time, LogLevel level, int screenId, string mod, string text) { if (this.Started) throw new InvalidOperationException("Can't start new message, previous log message isn't done yet."); @@ -48,6 +52,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing this.Time = time; this.Level = level; + this.ScreenId = screenId; this.Mod = mod; this.Text.Append(text); } @@ -74,6 +79,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing { Time = this.Time, Level = this.Level, + ScreenId = this.ScreenId, Mod = this.Mod, Text = this.Text.ToString() }; diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 227dcd89..f69d4b6f 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing ** Fields *********/ /// A regex pattern matching the start of a SMAPI message. - private readonly Regex MessageHeaderPattern = new Regex(@"^\[(?