summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCore.cs (renamed from src/SMAPI/Framework/SContentManager.cs)186
-rw-r--r--src/SMAPI/Framework/ContentManagerShim.cs68
-rw-r--r--src/SMAPI/Framework/GameVersion.cs20
-rw-r--r--src/SMAPI/Framework/ModData/ModDataField.cs2
-rw-r--r--src/SMAPI/Framework/ModData/ModDataRecord.cs11
-rw-r--r--src/SMAPI/Framework/ModData/ModDatabase.cs2
-rw-r--r--src/SMAPI/Framework/ModData/ParsedModDataRecord.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs43
-rw-r--r--src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs3
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs32
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs157
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs109
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs5
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs18
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs63
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedField.cs5
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedMethod.cs5
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedProperty.cs5
-rw-r--r--src/SMAPI/Framework/SGame.cs672
-rw-r--r--src/SMAPI/Framework/WebApiClient.cs2
21 files changed, 1277 insertions, 135 deletions
diff --git a/src/SMAPI/Framework/SContentManager.cs b/src/SMAPI/Framework/ContentCore.cs
index fa51bd53..85b8db8f 100644
--- a/src/SMAPI/Framework/SContentManager.cs
+++ b/src/SMAPI/Framework/ContentCore.cs
@@ -19,9 +19,9 @@ using StardewValley;
namespace StardewModdingAPI.Framework
{
- /// <summary>A thread-safe content manager which intercepts assets being loaded to let SMAPI mods inject or edit them.</summary>
+ /// <summary>A thread-safe content handler which loads assets with support for mod injection and editing.</summary>
/// <remarks>
- /// This is the centralised content manager which manages all game assets. The game and mods don't use this class
+ /// This is the centralised content logic which manages all game assets. The game and mods don't use this class
/// directly; instead they use one of several <see cref="ContentManagerShim"/> instances, which proxy requests to
/// this class. That ensures that when the game disposes one content manager, the others can continue unaffected.
/// That notably requires this class to be thread-safe, since the content managers can be disposed asynchronously.
@@ -30,22 +30,25 @@ namespace StardewModdingAPI.Framework
/// For English and non-translatable assets, these have the same value. The underlying cache only knows about asset
/// keys, and the game and mods only know about asset names. The content manager handles resolving them.
/// </remarks>
- internal class SContentManager : LocalizedContentManager
+ internal class ContentCore : IDisposable
{
/*********
** Properties
*********/
+ /// <summary>The underlying content manager.</summary>
+ private readonly LocalizedContentManager Content;
+
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
/// <summary>The underlying asset cache.</summary>
private readonly ContentCache Cache;
- /// <summary>The private <see cref="LocalizedContentManager"/> method which generates the locale portion of an asset name.</summary>
- private readonly IReflectedMethod GetKeyLocale;
+ /// <summary>The locale codes used in asset keys indexed by enum value.</summary>
+ private readonly IDictionary<LocalizedContentManager.LanguageCode, string> Locales;
- /// <summary>The language codes used in asset keys.</summary>
- private readonly IDictionary<string, LanguageCode> KeyLocales;
+ /// <summary>The language enum values indexed by locale code.</summary>
+ private readonly IDictionary<string, LocalizedContentManager.LanguageCode> LanguageCodes;
/// <summary>Provides metadata for core game assets.</summary>
private readonly CoreAssets CoreAssets;
@@ -66,15 +69,17 @@ namespace StardewModdingAPI.Framework
/*********
** Accessors
*********/
+ /// <summary>The current language as a constant.</summary>
+ public LocalizedContentManager.LanguageCode Language => this.Content.GetCurrentLanguage();
+
/// <summary>Interceptors which provide the initial versions of matching assets.</summary>
- internal IDictionary<IModMetadata, IList<IAssetLoader>> Loaders { get; } = new Dictionary<IModMetadata, IList<IAssetLoader>>();
+ public IDictionary<IModMetadata, IList<IAssetLoader>> Loaders { get; } = new Dictionary<IModMetadata, IList<IAssetLoader>>();
/// <summary>Interceptors which edit matching assets after they're loaded.</summary>
- internal IDictionary<IModMetadata, IList<IAssetEditor>> Editors { get; } = new Dictionary<IModMetadata, IList<IAssetEditor>>();
+ public IDictionary<IModMetadata, IList<IAssetEditor>> Editors { get; } = new Dictionary<IModMetadata, IList<IAssetEditor>>();
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
- internal string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.RootDirectory);
-
+ public string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.Content.RootDirectory);
/*********
** Public methods
@@ -89,18 +94,26 @@ namespace StardewModdingAPI.Framework
/// <param name="languageCodeOverride">The current language code for which to localise content.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="reflection">Simplifies access to private code.</param>
- public SContentManager(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, string languageCodeOverride, IMonitor monitor, Reflector reflection)
- : base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride)
+ public ContentCore(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, string languageCodeOverride, IMonitor monitor, Reflector reflection)
{
// init
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
- this.Cache = new ContentCache(this, reflection);
- this.GetKeyLocale = reflection.GetMethod(this, "languageCode");
+ this.Content = new LocalizedContentManager(serviceProvider, rootDirectory, currentCulture, languageCodeOverride);
+ this.Cache = new ContentCache(this.Content, reflection);
this.ModContentPrefix = this.GetAssetNameFromFilePath(Constants.ModPath);
// get asset data
- this.CoreAssets = new CoreAssets(this.NormaliseAssetName);
- this.KeyLocales = this.GetKeyLocales(reflection);
+ this.CoreAssets = new CoreAssets(this.NormaliseAssetName, reflection);
+ this.Locales = this.GetKeyLocales(reflection);
+ this.LanguageCodes = this.Locales.ToDictionary(p => p.Value, p => p.Key, StringComparer.InvariantCultureIgnoreCase);
+ }
+
+ /// <summary>Get a new content manager which defers loading to the content core.</summary>
+ /// <param name="name">The content manager's name for logs (if any).</param>
+ /// <param name="rootDirectory">The root directory to search for content (or <c>null</c>. for the default)</param>
+ public ContentManagerShim CreateContentManager(string name, string rootDirectory = null)
+ {
+ return new ContentManagerShim(this, name, this.Content.ServiceProvider, rootDirectory ?? this.Content.RootDirectory, this.Content.CurrentCulture, this.Content.LanguageCodeOverride);
}
/****
@@ -153,7 +166,14 @@ namespace StardewModdingAPI.Framework
/// <summary>Get the current content locale.</summary>
public string GetLocale()
{
- return this.GetKeyLocale.Invoke<string>();
+ return this.GetLocale(this.Content.GetCurrentLanguage());
+ }
+
+ /// <summary>The locale for a language.</summary>
+ /// <param name="language">The language.</param>
+ public string GetLocale(LocalizedContentManager.LanguageCode language)
+ {
+ return this.Locales[language];
}
/// <summary>Get whether the content manager has already loaded and cached the given asset.</summary>
@@ -177,18 +197,15 @@ namespace StardewModdingAPI.Framework
/// <summary>Load an asset through the content pipeline. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
/// <typeparam name="T">The expected asset type.</typeparam>
/// <param name="assetName">The asset path relative to the content directory.</param>
- public override T Load<T>(string assetName)
- {
- return this.LoadFor<T>(assetName, this);
- }
-
- /// <summary>Load an asset through the content pipeline. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
- /// <typeparam name="T">The expected asset type.</typeparam>
- /// <param name="assetName">The asset path relative to the content directory.</param>
/// <param name="instance">The content manager instance for which to load the asset.</param>
+ /// <param name="language">The language code for which to load content.</param>
/// <exception cref="ArgumentException">The <paramref name="assetName"/> is empty or contains invalid characters.</exception>
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
- public T LoadFor<T>(string assetName, ContentManager instance)
+ public T Load<T>(string assetName, ContentManager instance
+#if STARDEW_VALLEY_1_3
+ , LocalizedContentManager.LanguageCode language
+#endif
+ )
{
// normalise asset key
this.AssertValidAssetKeyFormat(assetName);
@@ -196,7 +213,11 @@ namespace StardewModdingAPI.Framework
// load game content
if (!assetName.StartsWith(this.ModContentPrefix))
+#if STARDEW_VALLEY_1_3
+ return this.LoadImpl<T>(assetName, instance, language);
+#else
return this.LoadImpl<T>(assetName, instance);
+#endif
// load mod content
SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading content asset '{assetName}': {reasonPhrase}");
@@ -206,7 +227,11 @@ namespace StardewModdingAPI.Framework
{
// try cache
if (this.IsLoaded(assetName))
+#if STARDEW_VALLEY_1_3
+ return this.LoadImpl<T>(assetName, instance, language);
+#else
return this.LoadImpl<T>(assetName, instance);
+#endif
// get file
FileInfo file = this.GetModFile(assetName);
@@ -218,7 +243,11 @@ namespace StardewModdingAPI.Framework
{
// XNB file
case ".xnb":
+#if STARDEW_VALLEY_1_3
+ return this.LoadImpl<T>(assetName, instance, language);
+#else
return this.LoadImpl<T>(assetName, instance);
+#endif
// unpacked map
case ".tbin":
@@ -339,7 +368,7 @@ namespace StardewModdingAPI.Framework
int reloaded = 0;
foreach (string key in removeAssetNames)
{
- if (this.CoreAssets.ReloadForKey(this, key))
+ if (this.CoreAssets.ReloadForKey(Game1.content, key)) // use an intercepted content manager
reloaded++;
}
@@ -379,11 +408,10 @@ namespace StardewModdingAPI.Framework
** Disposal
****/
/// <summary>Dispose held resources.</summary>
- /// <param name="disposing">Whether the content manager is disposing (rather than finalising).</param>
- protected override void Dispose(bool disposing)
+ public void Dispose()
{
this.Monitor.Log("Disposing SMAPI's main content manager. It will no longer be usable after this point.", LogLevel.Trace);
- base.Dispose(disposing);
+ this.Content.Dispose();
}
/****
@@ -398,29 +426,48 @@ namespace StardewModdingAPI.Framework
/// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
/// <param name="reflection">Simplifies access to private game code.</param>
- private IDictionary<string, LanguageCode> GetKeyLocales(Reflector reflection)
+ private IDictionary<LocalizedContentManager.LanguageCode, string> GetKeyLocales(Reflector reflection)
{
- // get the private code field directly to avoid changed-code logic
- IReflectedField<LanguageCode> codeField = reflection.GetField<LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
-
- // remember previous settings
- LanguageCode previousCode = codeField.GetValue();
- string previousOverride = this.LanguageCodeOverride;
+#if !STARDEW_VALLEY_1_3
+ IReflectedField<LocalizedContentManager.LanguageCode> codeField = reflection.GetField<LocalizedContentManager.LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
+ LocalizedContentManager.LanguageCode previousCode = codeField.GetValue();
+#endif
+ string previousOverride = this.Content.LanguageCodeOverride;
- // create locale => code map
- IDictionary<string, LanguageCode> map = new Dictionary<string, LanguageCode>(StringComparer.InvariantCultureIgnoreCase);
- this.LanguageCodeOverride = null;
- foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
+ try
{
- codeField.SetValue(code);
- map[this.GetKeyLocale.Invoke<string>()] = code;
- }
+ // temporarily disable language override
+ this.Content.LanguageCodeOverride = null;
+
+ // create locale => code map
+ IReflectedMethod languageCodeString = reflection
+#if STARDEW_VALLEY_1_3
+ .GetMethod(this.Content, "languageCodeString");
+#else
+ .GetMethod(this.Content, "languageCode");
+#endif
+ IDictionary<LocalizedContentManager.LanguageCode, string> map = new Dictionary<LocalizedContentManager.LanguageCode, string>();
+ foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode)))
+ {
+#if STARDEW_VALLEY_1_3
+ map[code] = languageCodeString.Invoke<string>(code);
+#else
+ codeField.SetValue(code);
+ map[code] = languageCodeString.Invoke<string>();
+#endif
+ }
- // restore previous settings
- codeField.SetValue(previousCode);
- this.LanguageCodeOverride = previousOverride;
+ return map;
+ }
+ finally
+ {
+ // restore previous settings
+ this.Content.LanguageCodeOverride = previousOverride;
+#if !STARDEW_VALLEY_1_3
+ codeField.SetValue(previousCode);
+#endif
- return map;
+ }
}
/// <summary>Get the asset name from a cache key.</summary>
@@ -444,7 +491,7 @@ namespace StardewModdingAPI.Framework
if (lastSepIndex >= 0)
{
string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
- if (this.KeyLocales.ContainsKey(suffix))
+ if (this.LanguageCodes.ContainsKey(suffix))
{
assetName = cacheKey.Substring(0, lastSepIndex);
localeCode = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
@@ -466,7 +513,7 @@ namespace StardewModdingAPI.Framework
private bool IsNormalisedKeyLoaded(string normalisedAssetName)
{
return this.Cache.ContainsKey(normalisedAssetName)
- || this.Cache.ContainsKey($"{normalisedAssetName}.{this.GetKeyLocale.Invoke<string>()}"); // translated asset
+ || this.Cache.ContainsKey($"{normalisedAssetName}.{this.Locales[this.Content.GetCurrentLanguage()]}"); // translated asset
}
/// <summary>Track that a content manager loaded an asset.</summary>
@@ -486,7 +533,12 @@ namespace StardewModdingAPI.Framework
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
/// <param name="instance">The content manager instance for which to load the asset.</param>
- private T LoadImpl<T>(string assetName, ContentManager instance)
+ /// <param name="language">The language code for which to load content.</param>
+ private T LoadImpl<T>(string assetName, ContentManager instance
+#if STARDEW_VALLEY_1_3
+ , LocalizedContentManager.LanguageCode language
+#endif
+ )
{
return this.WithWriteLock(() =>
{
@@ -494,7 +546,13 @@ namespace StardewModdingAPI.Framework
if (this.IsNormalisedKeyLoaded(assetName))
{
this.TrackAssetLoader(assetName, instance);
- return base.Load<T>(assetName);
+ return this.Content
+
+#if STARDEW_VALLEY_1_3
+ .Load<T>(assetName, language);
+#else
+ .Load<T>(assetName);
+#endif
}
// load asset
@@ -503,14 +561,30 @@ namespace StardewModdingAPI.Framework
{
this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn);
this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace);
- data = base.Load<T>(assetName);
+ data = this.Content
+#if STARDEW_VALLEY_1_3
+ .Load<T>(assetName, language);
+#else
+ .Load<T>(assetName);
+#endif
}
else
{
data = this.AssetsBeingLoaded.Track(assetName, () =>
{
- IAssetInfo info = new AssetInfo(this.GetLocale(), assetName, typeof(T), this.NormaliseAssetName);
- IAssetData asset = this.ApplyLoader<T>(info) ?? new AssetDataForObject(info, base.Load<T>(assetName), this.NormaliseAssetName);
+ string locale =
+#if STARDEW_VALLEY_1_3
+ this.GetLocale(language);
+#else
+ this.GetLocale();
+#endif
+ IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.NormaliseAssetName);
+ IAssetData asset = this.ApplyLoader<T>(info)
+#if STARDEW_VALLEY_1_3
+ ?? new AssetDataForObject(info, this.Content.Load<T>(assetName, language), this.NormaliseAssetName);
+#else
+ ?? new AssetDataForObject(info, this.Content.Load<T>(assetName), this.NormaliseAssetName);
+#endif
asset = this.ApplyEditors<T>(info, asset);
return (T)asset.Data;
});
diff --git a/src/SMAPI/Framework/ContentManagerShim.cs b/src/SMAPI/Framework/ContentManagerShim.cs
index d46f23a3..8f88fc2d 100644
--- a/src/SMAPI/Framework/ContentManagerShim.cs
+++ b/src/SMAPI/Framework/ContentManagerShim.cs
@@ -1,15 +1,17 @@
+using System;
+using System.Globalization;
using StardewValley;
namespace StardewModdingAPI.Framework
{
- /// <summary>A minimal content manager which defers to SMAPI's main content manager.</summary>
+ /// <summary>A minimal content manager which defers to SMAPI's core content logic.</summary>
internal class ContentManagerShim : LocalizedContentManager
{
/*********
** Properties
*********/
- /// <summary>SMAPI's underlying content manager.</summary>
- private readonly SContentManager ContentManager;
+ /// <summary>SMAPI's core content logic.</summary>
+ private readonly ContentCore ContentCore;
/*********
@@ -23,12 +25,16 @@ namespace StardewModdingAPI.Framework
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="contentManager">SMAPI's underlying content manager.</param>
+ /// <param name="contentCore">SMAPI's core content logic.</param>
/// <param name="name">The content manager's name for logs (if any).</param>
- public ContentManagerShim(SContentManager contentManager, string name)
- : base(contentManager.ServiceProvider, contentManager.RootDirectory, contentManager.CurrentCulture, contentManager.LanguageCodeOverride)
+ /// <param name="serviceProvider">The service provider to use to locate services.</param>
+ /// <param name="rootDirectory">The root directory to search for content.</param>
+ /// <param name="currentCulture">The current culture for which to localise content.</param>
+ /// <param name="languageCodeOverride">The current language code for which to localise content.</param>
+ public ContentManagerShim(ContentCore contentCore, string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, string languageCodeOverride)
+ : base(serviceProvider, rootDirectory, currentCulture, languageCodeOverride)
{
- this.ContentManager = contentManager;
+ this.ContentCore = contentCore;
this.Name = name;
}
@@ -37,14 +43,58 @@ namespace StardewModdingAPI.Framework
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
public override T Load<T>(string assetName)
{
- return this.ContentManager.LoadFor<T>(assetName, this);
+#if STARDEW_VALLEY_1_3
+ return this.Load<T>(assetName, LocalizedContentManager.CurrentLanguageCode);
+#else
+ return this.ContentCore.Load<T>(assetName, this);
+#endif
}
+#if STARDEW_VALLEY_1_3
+ /// <summary>Load an asset that has been processed by the content pipeline.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="language">The language code for which to load content.</param>
+ public override T Load<T>(string assetName, LanguageCode language)
+ {
+ return this.ContentCore.Load<T>(assetName, this, language);
+ }
+
+ /// <summary>Load the base asset without localisation.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ public override T LoadBase<T>(string assetName)
+ {
+ return this.Load<T>(assetName, LanguageCode.en);
+ }
+#endif
+
+ /// <summary>Inject an asset into the cache.</summary>
+ /// <typeparam name="T">The type of asset to inject.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="value">The asset value.</param>
+ public void Inject<T>(string assetName, T value)
+ {
+ this.ContentCore.Inject<T>(assetName, value, this);
+ }
+
+#if STARDEW_VALLEY_1_3
+ /// <summary>Create a new content manager for temporary use.</summary>
+ public override LocalizedContentManager CreateTemporary()
+ {
+ return this.ContentCore.CreateContentManager("(temporary)");
+ }
+#endif
+
+
+ /*********
+ ** Protected methods
+ *********/
/// <summary>Dispose held resources.</summary>
/// <param name="disposing">Whether the content manager is disposing (rather than finalising).</param>
protected override void Dispose(bool disposing)
{
- this.ContentManager.DisposeFor(this);
+ this.ContentCore.DisposeFor(this);
}
}
}
diff --git a/src/SMAPI/Framework/GameVersion.cs b/src/SMAPI/Framework/GameVersion.cs
index 1884afe9..e5022212 100644
--- a/src/SMAPI/Framework/GameVersion.cs
+++ b/src/SMAPI/Framework/GameVersion.cs
@@ -49,21 +49,31 @@ namespace StardewModdingAPI.Framework
/// <param name="gameVersion">The game version string.</param>
private static string GetSemanticVersionString(string gameVersion)
{
+#if STARDEW_VALLEY_1_3
+ if(gameVersion.StartsWith("1.3.0."))
+ return new SemanticVersion(1, 3, 0, "alpha." + gameVersion.Substring("1.3.0.".Length)).ToString();
+#endif
+
return GameVersion.VersionMap.TryGetValue(gameVersion, out string semanticVersion)
? semanticVersion
: gameVersion;
}
- /// <summary>Convert a game version string to a semantic version string.</summary>
- /// <param name="gameVersion">The game version string.</param>
- private static string GetGameVersionString(string gameVersion)
+ /// <summary>Convert a semantic version string to the equivalent game version string.</summary>
+ /// <param name="semanticVersion">The semantic version string.</param>
+ private static string GetGameVersionString(string semanticVersion)
{
+ #if STARDEW_VALLEY_1_3
+ if(semanticVersion.StartsWith("1.3-alpha."))
+ return "1.3.0." + semanticVersion.Substring("1.3-alpha.".Length);
+ #endif
+
foreach (var mapping in GameVersion.VersionMap)
{
- if (mapping.Value.Equals(gameVersion, StringComparison.InvariantCultureIgnoreCase))
+ if (mapping.Value.Equals(semanticVersion, StringComparison.InvariantCultureIgnoreCase))
return mapping.Key;
}
- return gameVersion;
+ return semanticVersion;
}
}
}
diff --git a/src/SMAPI/Framework/ModData/ModDataField.cs b/src/SMAPI/Framework/ModData/ModDataField.cs
index fa8dd6d0..df906103 100644
--- a/src/SMAPI/Framework/ModData/ModDataField.cs
+++ b/src/SMAPI/Framework/ModData/ModDataField.cs
@@ -66,7 +66,7 @@ namespace StardewModdingAPI.Framework.ModData
{
// update key
case ModDataFieldKey.UpdateKey:
- return manifest.UpdateKeys != null && manifest.UpdateKeys.Any();
+ return manifest.UpdateKeys != null && manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p));
// non-manifest fields
case ModDataFieldKey.AlternativeUrl:
diff --git a/src/SMAPI/Framework/ModData/ModDataRecord.cs b/src/SMAPI/Framework/ModData/ModDataRecord.cs
index 79a954f7..56275f53 100644
--- a/src/SMAPI/Framework/ModData/ModDataRecord.cs
+++ b/src/SMAPI/Framework/ModData/ModDataRecord.cs
@@ -106,10 +106,10 @@ namespace StardewModdingAPI.Framework.ModData
/// <summary>Get a semantic local version for update checks.</summary>
/// <param name="version">The remote version to normalise.</param>
- public string GetLocalVersionForUpdateChecks(string version)
+ public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
{
- return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version, out string newVersion)
- ? newVersion
+ return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
+ ? new SemanticVersion(newVersion)
: version;
}
@@ -117,6 +117,11 @@ namespace StardewModdingAPI.Framework.ModData
/// <param name="version">The remote version to normalise.</param>
public string GetRemoteVersionForUpdateChecks(string version)
{
+ // normalise version if possible
+ if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
+ version = parsed.ToString();
+
+ // fetch remote version
return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion)
? newVersion
: version;
diff --git a/src/SMAPI/Framework/ModData/ModDatabase.cs b/src/SMAPI/Framework/ModData/ModDatabase.cs
index 332c5c48..3fd68440 100644
--- a/src/SMAPI/Framework/ModData/ModDatabase.cs
+++ b/src/SMAPI/Framework/ModData/ModDatabase.cs
@@ -157,7 +157,7 @@ namespace StardewModdingAPI.Framework.ModData
&& (
snapshot.Author == null
|| snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase)
- || (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase))
+ || (manifest.ExtraFields != null && manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase))
)
&& (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase));
diff --git a/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
index 7f49790d..deb12bdc 100644
--- a/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
+++ b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModData
*********/
/// <summary>Get a semantic local version for update checks.</summary>
/// <param name="version">The remote version to normalise.</param>
- public string GetLocalVersionForUpdateChecks(string version)
+ public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
{
return this.DataRecord.GetLocalVersionForUpdateChecks(version);
}
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 7d8bec1e..c7d4c39e 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -22,8 +22,11 @@ namespace StardewModdingAPI.Framework.ModHelpers
/*********
** Properties
*********/
- /// <summary>SMAPI's underlying content manager.</summary>
- private readonly SContentManager ContentManager;
+ /// <summary>SMAPI's core content logic.</summary>
+ private readonly ContentCore ContentCore;
+
+ /// <summary>The content manager for this mod.</summary>
+ private readonly ContentManagerShim ContentManager;
/// <summary>The absolute path to the mod folder.</summary>
private readonly string ModFolderPath;
@@ -39,10 +42,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
** Accessors
*********/
/// <summary>The game's current locale code (like <c>pt-BR</c>).</summary>
- public string CurrentLocale => this.ContentManager.GetLocale();
+ public string CurrentLocale => this.ContentCore.GetLocale();
/// <summary>The game's current locale as an enum value.</summary>
- public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.ContentManager.GetCurrentLanguage();
+ public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.ContentCore.Language;
/// <summary>The observable implementation of <see cref="AssetEditors"/>.</summary>
internal ObservableCollection<IAssetEditor> ObservableAssetEditors { get; } = new ObservableCollection<IAssetEditor>();
@@ -61,14 +64,16 @@ namespace StardewModdingAPI.Framework.ModHelpers
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="contentManager">SMAPI's underlying content manager.</param>
+ /// <param name="contentCore">SMAPI's core content logic.</param>
+ /// <param name="contentManager">The content manager for this mod.</param>
/// <param name="modFolderPath">The absolute path to the mod folder.</param>
/// <param name="modID">The unique ID of the relevant mod.</param>
/// <param name="modName">The friendly mod name for use in errors.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public ContentHelper(SContentManager contentManager, string modFolderPath, string modID, string modName, IMonitor monitor)
+ public ContentHelper(ContentCore contentCore, ContentManagerShim contentManager, string modFolderPath, string modID, string modName, IMonitor monitor)
: base(modID)
{
+ this.ContentCore = contentCore;
this.ContentManager = contentManager;
this.ModFolderPath = modFolderPath;
this.ModName = modName;
@@ -100,10 +105,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
throw GetContentError($"there's no matching file at path '{file.FullName}'.");
// get asset path
- string assetName = this.ContentManager.GetAssetNameFromFilePath(file.FullName);
+ string assetName = this.ContentCore.GetAssetNameFromFilePath(file.FullName);
// try cache
- if (this.ContentManager.IsLoaded(assetName))
+ if (this.ContentCore.IsLoaded(assetName))