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 | ||||
-rw-r--r-- | src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs | 10 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 37 |
5 files changed, 214 insertions, 108 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; 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."); } 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" ] |