summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Web')
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs54
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs15
-rw-r--r--src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs3
-rw-r--r--src/SMAPI.Web/Framework/VersionConstraint.cs2
-rw-r--r--src/SMAPI.Web/SMAPI.Web.csproj6
-rw-r--r--src/SMAPI.Web/Views/Mods/Index.cshtml4
-rw-r--r--src/SMAPI.Web/appsettings.json13
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/mods.css5
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/mods.js24
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json10
-rw-r--r--src/SMAPI.Web/wwwroot/schemas/content-patcher.json8
11 files changed, 94 insertions, 50 deletions
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index f194b4d0..06768f03 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -41,11 +41,8 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>The cache in which to store mod data.</summary>
private readonly IModCacheRepository ModCache;
- /// <summary>The number of minutes successful update checks should be cached before refetching them.</summary>
- private readonly int SuccessCacheMinutes;
-
- /// <summary>The number of minutes failed update checks should be cached before refetching them.</summary>
- private readonly int ErrorCacheMinutes;
+ /// <summary>The config settings for mod update checks.</summary>
+ private readonly IOptions<ModUpdateCheckConfig> Config;
/// <summary>The internal mod metadata list.</summary>
private readonly ModDatabase ModDatabase;
@@ -58,21 +55,19 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="environment">The web hosting environment.</param>
/// <param name="wikiCache">The cache in which to store wiki data.</param>
/// <param name="modCache">The cache in which to store mod metadata.</param>
- /// <param name="configProvider">The config settings for mod update checks.</param>
+ /// <param name="config">The config settings for mod update checks.</param>
/// <param name="chucklefish">The Chucklefish API client.</param>
/// <param name="curseForge">The CurseForge API client.</param>
/// <param name="github">The GitHub API client.</param>
/// <param name="modDrop">The ModDrop API client.</param>
/// <param name="nexus">The Nexus API client.</param>
- public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions<ModUpdateCheckConfig> configProvider, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus)
+ public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions<ModUpdateCheckConfig> config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus)
{
this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json"));
- ModUpdateCheckConfig config = configProvider.Value;
this.WikiCache = wikiCache;
this.ModCache = modCache;
- this.SuccessCacheMinutes = config.SuccessCacheMinutes;
- this.ErrorCacheMinutes = config.ErrorCacheMinutes;
+ this.Config = config;
this.Repositories =
new IModRepository[]
{
@@ -133,6 +128,8 @@ namespace StardewModdingAPI.Web.Controllers
ModDataRecord record = this.ModDatabase.Get(search.ID);
WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase));
UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray();
+ ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.InvariantCultureIgnoreCase));
+ bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false;
// get latest versions
ModEntryModel result = new ModEntryModel { ID = search.ID };
@@ -151,7 +148,7 @@ namespace StardewModdingAPI.Web.Controllers
}
// fetch data
- ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey);
+ ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions);
if (data.Error != null)
{
errors.Add(data.Error);
@@ -161,7 +158,7 @@ namespace StardewModdingAPI.Web.Controllers
// handle main version
if (data.Version != null)
{
- ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions);
+ ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
if (version == null)
{
errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'.");
@@ -175,7 +172,7 @@ namespace StardewModdingAPI.Web.Controllers
// handle optional version
if (data.PreviewVersion != null)
{
- ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions);
+ ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
if (version == null)
{
errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'.");
@@ -215,16 +212,16 @@ namespace StardewModdingAPI.Web.Controllers
}
// special cases
- if (result.ID == "Pathoschild.SMAPI")
+ if (overrides?.SetUrl != null)
{
if (main != null)
- main.Url = "https://smapi.io/";
+ main.Url = overrides.SetUrl;
if (optional != null)
- optional.Url = "https://smapi.io/";
+ optional.Url = overrides.SetUrl;
}
// get recommended update (if any)
- ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions);
+ ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions);
if (apiVersion != null && installedVersion != null)
{
// get newer versions
@@ -283,10 +280,11 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>Get the mod info for an update key.</summary>
/// <param name="updateKey">The namespaced update key.</param>
- private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey)
+ /// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
+ private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions)
{
// get mod
- if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.ErrorCacheMinutes : this.SuccessCacheMinutes))
+ if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes))
{
// get site
if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository))
@@ -298,7 +296,7 @@ namespace StardewModdingAPI.Web.Controllers
{
if (result.Version == null)
result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number.");
- else if (!SemanticVersion.TryParse(result.Version, out _))
+ else if (!SemanticVersion.TryParse(result.Version, allowNonStandardVersions, out _))
result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'.");
}
@@ -357,15 +355,16 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>Get a semantic local version for update checks.</summary>
/// <param name="version">The version to parse.</param>
/// <param name="map">A map of version replacements.</param>
- private ISemanticVersion GetMappedVersion(string version, IDictionary<string, string> map)
+ /// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
+ private ISemanticVersion GetMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard)
{
// try mapped version
- string rawNewVersion = this.GetRawMappedVersion(version, map);
- if (SemanticVersion.TryParse(rawNewVersion, out ISemanticVersion parsedNew))
+ string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard);
+ if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew))
return parsedNew;
// return original version
- return SemanticVersion.TryParse(version, out ISemanticVersion parsedOld)
+ return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsedOld)
? parsedOld
: null;
}
@@ -373,7 +372,8 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>Get a semantic local version for update checks.</summary>
/// <param name="version">The version to map.</param>
/// <param name="map">A map of version replacements.</param>
- private string GetRawMappedVersion(string version, IDictionary<string, string> map)
+ /// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
+ private string GetRawMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard)
{
if (version == null || map == null || !map.Any())
return version;
@@ -383,14 +383,14 @@ namespace StardewModdingAPI.Web.Controllers
return map[version];
// match parsed version
- if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
+ if (SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsed))
{
if (map.ContainsKey(parsed.ToString()))
return map[parsed.ToString()];
foreach (var pair in map)
{
- if (SemanticVersion.TryParse(pair.Key, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, out ISemanticVersion newVersion))
+ if (SemanticVersion.TryParse(pair.Key, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, allowNonStandard, out ISemanticVersion newVersion))
return newVersion.ToString();
}
}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs
new file mode 100644
index 00000000..f382d7b5
--- /dev/null
+++ b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.ConfigModels
+{
+ /// <summary>Override update-check metadata for a mod.</summary>
+ internal class ModOverrideConfig
+ {
+ /// <summary>The unique ID from the mod's manifest.</summary>
+ public string ID { get; set; }
+
+ /// <summary>Whether to allow non-standard versions.</summary>
+ public bool AllowNonStandardVersions { get; set; }
+
+ /// <summary>The mod page URL to use regardless of which site has the update, or <c>null</c> to use the site URL.</summary>
+ public string SetUrl { get; set; }
+ }
+}
diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
index 46073eb8..bd58dba0 100644
--- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
+++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
@@ -11,5 +11,8 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/// <summary>The number of minutes failed update checks should be cached before refetching them.</summary>
public int ErrorCacheMinutes { get; set; }
+
+ /// <summary>Update-check metadata to override.</summary>
+ public ModOverrideConfig[] ModOverrides { get; set; }
}
}
diff --git a/src/SMAPI.Web/Framework/VersionConstraint.cs b/src/SMAPI.Web/Framework/VersionConstraint.cs
index 72f5ef84..f0c57c41 100644
--- a/src/SMAPI.Web/Framework/VersionConstraint.cs
+++ b/src/SMAPI.Web/Framework/VersionConstraint.cs
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Web.Framework
return
values.TryGetValue(routeKey, out object routeValue)
&& routeValue is string routeStr
- && SemanticVersion.TryParseNonStandard(routeStr, out _);
+ && SemanticVersion.TryParse(routeStr, allowNonStandard: true, out _);
}
}
}
diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj
index 148631a9..97bea0fb 100644
--- a/src/SMAPI.Web/SMAPI.Web.csproj
+++ b/src/SMAPI.Web/SMAPI.Web.csproj
@@ -12,11 +12,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Azure.Storage.Blobs" Version="12.2.0" />
+ <PackageReference Include="Azure.Storage.Blobs" Version="12.3.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.9" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.6.3" />
<PackageReference Include="Hangfire.Mongo" Version="0.6.6" />
- <PackageReference Include="HtmlAgilityPack" Version="1.11.18" />
+ <PackageReference Include="HtmlAgilityPack" Version="1.11.20" />
<PackageReference Include="Humanizer.Core" Version="2.7.9" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Markdig" Version="0.18.1" />
@@ -25,7 +25,7 @@
<PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Mongo2Go" Version="2.2.12" />
- <PackageReference Include="MongoDB.Driver" Version="2.10.1" />
+ <PackageReference Include="MongoDB.Driver" Version="2.10.2" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
<PackageReference Include="Pathoschild.FluentNexus" Version="0.8.0" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" />
diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml
index 5b310d55..b1d9ae2c 100644
--- a/src/SMAPI.Web/Views/Mods/Index.cshtml
+++ b/src/SMAPI.Web/Views/Mods/Index.cshtml
@@ -8,11 +8,11 @@
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
}
@section Head {
- <link rel="stylesheet" href="~/Content/css/mods.css?r=20190302" />
+ <link rel="stylesheet" href="~/Content/css/mods.css?r=20200218" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/tablesorter@2.31.0/dist/js/jquery.tablesorter.combined.min.js" crossorigin="anonymous"></script>
- <script src="~/Content/js/mods.js?r=20190302"></script>
+ <script src="~/Content/js/mods.js?r=20200218"></script>
<script>
$(function() {
var data = @Json.Serialize(Model.Mods, new JsonSerializerSettings { Formatting = Formatting.None });
diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json
index caeb381f..9cd1efc8 100644
--- a/src/SMAPI.Web/appsettings.json
+++ b/src/SMAPI.Web/appsettings.json
@@ -64,6 +64,17 @@
"ModUpdateCheck": {
"SuccessCacheMinutes": 60,
- "ErrorCacheMinutes": 5
+ "ErrorCacheMinutes": 5,
+ "ModOverrides": [
+ {
+ "ID": "Pathoschild.SMAPI",
+ "AllowNonStandardVersions": true,
+ "SetUrl": "https://smapi.io"
+ },
+ {
+ "ID": "MartyrPher.SMAPI-Android-Installer",
+ "AllowNonStandardVersions": true
+ }
+ ]
}
}
diff --git a/src/SMAPI.Web/wwwroot/Content/css/mods.css b/src/SMAPI.Web/wwwroot/Content/css/mods.css
index 1c2b8056..697ba514 100644
--- a/src/SMAPI.Web/wwwroot/Content/css/mods.css
+++ b/src/SMAPI.Web/wwwroot/Content/css/mods.css
@@ -86,6 +86,11 @@ table.wikitable > caption {
font-size: 0.9em;
}
+#mod-list thead tr {
+ position: sticky;
+ top: 0;
+}
+
#mod-list th.header {
background-repeat: no-repeat;
background-position: center right;
diff --git a/src/SMAPI.Web/wwwroot/Content/js/mods.js b/src/SMAPI.Web/wwwroot/Content/js/mods.js
index 0394ac4f..35098b60 100644
--- a/src/SMAPI.Web/wwwroot/Content/js/mods.js
+++ b/src/SMAPI.Web/wwwroot/Content/js/mods.js
@@ -102,7 +102,7 @@ smapi.modList = function (mods, enableBeta) {
app = new Vue({
el: "#app",
data: data,
- mounted: function() {
+ mounted: function () {
// enable table sorting
$("#mod-list").tablesorter({
cssHeader: "header",
@@ -115,11 +115,7 @@ smapi.modList = function (mods, enableBeta) {
$("#search-box").focus();
// jump to anchor (since table is added after page load)
- if (location.hash) {
- var row = $(location.hash).get(0);
- if (row)
- row.scrollIntoView();
- }
+ this.fixHashPosition();
},
methods: {
/**
@@ -144,6 +140,18 @@ smapi.modList = function (mods, enableBeta) {
}
},
+ /**
+ * Fix the window position for the current hash.
+ */
+ fixHashPosition: function () {
+ if (!location.hash)
+ return;
+
+ var row = $(location.hash);
+ var target = row.prev().get(0) || row.get(0);
+ if (target)
+ target.scrollIntoView();
+ },
/**
* Get whether a mod matches the current filters.
@@ -151,7 +159,7 @@ smapi.modList = function (mods, enableBeta) {
* @param {string[]} searchWords The search words to match.
* @returns {bool} Whether the mod matches the filters.
*/
- matchesFilters: function(mod, searchWords) {
+ matchesFilters: function (mod, searchWords) {
var filters = data.filters;
// check hash
@@ -249,7 +257,9 @@ smapi.modList = function (mods, enableBeta) {
}
});
app.applyFilters();
+ app.fixHashPosition();
window.addEventListener("hashchange", function () {
app.applyFilters();
+ app.fixHashPosition();
});
};
diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
index 78918bac..3101fdf1 100644
--- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
+++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
@@ -112,7 +112,7 @@
"Default | UpdateKey": "Nexus:2341"
},
- "TMX Loader": {
+ "TMXL Map Toolkit": {
"ID": "Platonymous.TMXLoader",
"Default | UpdateKey": "Nexus:1820"
},
@@ -129,7 +129,7 @@
"Bee House Flower Range Fix": {
"ID": "kirbylink.beehousefix",
"~ | Status": "Obsolete",
- "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4."
+ "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4."
},
"Colored Chests": {
@@ -153,9 +153,9 @@
/*********
** Broke in SDV 1.4
*********/
- "Fix Dice": {
- "ID": "ashley.fixdice",
- "~1.1.2 | Status": "AssumeBroken" // crashes game on startup
+ "Auto Quality Patch": {
+ "ID": "SilentOak.AutoQualityPatch",
+ "~2.1.3-unofficial.7 | Status": "AssumeBroken" // runtime errors
},
"Fix Dice": {
diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
index 7e00c28e..e6cd4e65 100644
--- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
+++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
@@ -142,7 +142,7 @@
},
"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 (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'). 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": [
{
@@ -151,12 +151,12 @@
}
},
{
- "pattern": "\\.(json|png|tbin|xnb) *$"
+ "pattern": "\\.(json|png|tbin|tmx|xnb) *$"
}
],
"@errorMessages": {
"allOf:indexes: 0": "Invalid value; must not contain directory climbing (like '../').",
- "allOf:indexes: 1": "Invalid value; must be a file path ending with .json, .png, .tbin, or .xnb."
+ "allOf:indexes: 1": "Invalid value; must be a file path ending with .json, .png, .tbin, .tmx, or .xnb."
}
},
"FromArea": {
@@ -325,7 +325,7 @@
"then": {
"properties": {
"FromFile": {
- "description": "The relative path to the map in your content pack folder from which to copy (like assets/town.tbin). This can be a .tbin or .xnb file. This field supports tokens and capitalization doesn't matter.\nContent Patcher will handle tilesheets referenced by the FromFile map for you:\n - If a tilesheet isn't referenced by the target map, Content Patcher will add it for you (with a z_ ID prefix to avoid conflicts with hardcoded game logic). If the source map has a custom version of a tilesheet that's already referenced, it'll be added as a separate tilesheet only used by your tiles.\n - If you include the tilesheet file in your mod folder, Content Patcher will use that one automatically; otherwise it will be loaded from the game's Content/Maps folder."
+ "description": "The relative path to the map in your content pack folder from which to copy (like assets/town.tbin). This can be a .tbin, .tmx, or .xnb file. This field supports tokens and capitalization doesn't matter.\nContent Patcher will handle tilesheets referenced by the FromFile map for you:\n - If a tilesheet isn't referenced by the target map, Content Patcher will add it for you (with a z_ ID prefix to avoid conflicts with hardcoded game logic). If the source map has a custom version of a tilesheet that's already referenced, it'll be added as a separate tilesheet only used by your tiles.\n - If you include the tilesheet file in your mod folder, Content Patcher will use that one automatically; otherwise it will be loaded from the game's Content/Maps folder."
},
"FromArea": {
"description": "The part of the source map to copy. Defaults to the whole source map."