From cd93d59eaf5aa05e0fa55eadd958339b9042155d Mon Sep 17 00:00:00 2001 From: Tyler Staples Date: Mon, 12 Dec 2016 15:58:44 -0800 Subject: Added a struct to wrap cache entries for the sake of tracking invalid lookups. This fixes the issue where a null reference exception would be thrown when trying to look up non-existant or non-private members. Added a null check to GetPrivateValue and it's overloads to fix the issue where it would throw a null reference exception when required was false and the field was null. --- .../Framework/Reflection/ReflectionHelper.cs | 49 ++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 38b4e357..1d5cf157 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,6 +12,16 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ + /// MemberInfo wrapper for tracking validity. + internal struct CacheEntry + { + /// Is this member valid. Used to avoid unecessary lookups. + public bool IsValid; + + /// The reflection data for this member. This will be null if IsValid is false. + public MemberInfo MemberInfo; + } + /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -67,10 +77,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(object obj, string name, bool required = true) { - return this.GetPrivateField(obj, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(obj, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /// Get the value of a private static field. @@ -78,10 +95,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(Type type, string name, bool required = true) { - return this.GetPrivateField(type, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(type, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /**** @@ -228,11 +252,22 @@ namespace StardewModdingAPI.Framework.Reflection { // get from cache if (this.Cache.Contains(key)) - return (TMemberInfo)this.Cache[key]; + { + CacheEntry entry = (CacheEntry)this.Cache[key]; + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo + : default(TMemberInfo); + } - // fetch & cache new value + // fetch & cache new value, marking if it's valid for future lookups. TMemberInfo result = fetch(); - this.Cache.Add(key, result, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); + CacheEntry cacheEntry = new CacheEntry() + { + IsValid = (result != null), + MemberInfo = result + }; + + this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -- cgit From d9e87399bf65d0053ad57d316d0df9e1a631a42b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 12:27:44 -0500 Subject: format code (#193) --- .../Framework/Reflection/CacheEntry.cs | 30 ++++++++++++++++++++ .../Framework/Reflection/ReflectionHelper.cs | 33 ++++++---------------- 2 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs new file mode 100644 index 00000000..30faca37 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs @@ -0,0 +1,30 @@ +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A cached member reflection result. + internal struct CacheEntry + { + /********* + ** Accessors + *********/ + /// Whether the lookup found a valid match. + public bool IsValid; + + /// The reflection data for this member (or null if invalid). + public MemberInfo MemberInfo; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Whether the lookup found a valid match. + /// The reflection data for this member (or null if invalid). + public CacheEntry(bool isValid, MemberInfo memberInfo) + { + this.IsValid = isValid; + this.MemberInfo = memberInfo; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 1d5cf157..edf59b81 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,16 +12,6 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ - /// MemberInfo wrapper for tracking validity. - internal struct CacheEntry - { - /// Is this member valid. Used to avoid unecessary lookups. - public bool IsValid; - - /// The reflection data for this member. This will be null if IsValid is false. - public MemberInfo MemberInfo; - } - /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -77,7 +67,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -85,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(object obj, string name, bool required = true) { IPrivateField field = this.GetPrivateField(obj, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -95,7 +85,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -103,7 +93,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(Type type, string name, bool required = true) { IPrivateField field = this.GetPrivateField(type, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -254,21 +244,16 @@ namespace StardewModdingAPI.Framework.Reflection if (this.Cache.Contains(key)) { CacheEntry entry = (CacheEntry)this.Cache[key]; - return entry.IsValid - ? (TMemberInfo)entry.MemberInfo + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo : default(TMemberInfo); } - // fetch & cache new value, marking if it's valid for future lookups. + // fetch & cache new value TMemberInfo result = fetch(); - CacheEntry cacheEntry = new CacheEntry() - { - IsValid = (result != null), - MemberInfo = result - }; - + CacheEntry cacheEntry = new CacheEntry(result != null, result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -} \ No newline at end of file +} -- cgit From 23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 15:37:23 -0500 Subject: migrate manifest & version to interfaces with backwards compatibility (#197) --- src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs | 2 +- src/StardewModdingAPI/Framework/ModAssemblyLoader.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs index 3dfbc78c..a747eaa8 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// The paths for the cached assembly. /// The MD5 hash of the original assembly. /// The current SMAPI version. - public bool IsUpToDate(CachePaths paths, string hash, Version currentVersion) + public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion) { return hash == this.Hash && this.ApiVersion == currentVersion.ToString() diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs index 1ceb8ad2..a2c4ac23 100644 --- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework CachePaths cachePaths = this.GetCachePaths(assemblyPath); { CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject(File.ReadAllText(cachePaths.Metadata)) : null; - if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.Version)) + if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion)) return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed } this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace); @@ -99,7 +99,7 @@ namespace StardewModdingAPI.Framework // cache all results foreach (RewriteResult result in results) { - CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.Version.ToString(), forceCacheAssemblies || result.UseCachedAssembly); + CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), forceCacheAssemblies || result.UseCachedAssembly); File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry)); if (forceCacheAssemblies || result.UseCachedAssembly) File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes); -- cgit From f7eda265d9dd11af7bc9ce2540d45ba2668e2345 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Dec 2016 10:48:05 -0500 Subject: track loaded mod instances & manifests via mod registry (#201) --- src/StardewModdingAPI/Framework/ModRegistry.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/ModRegistry.cs b/src/StardewModdingAPI/Framework/ModRegistry.cs index ba56a447..b593142d 100644 --- a/src/StardewModdingAPI/Framework/ModRegistry.cs +++ b/src/StardewModdingAPI/Framework/ModRegistry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; namespace StardewModdingAPI.Framework @@ -11,6 +12,9 @@ namespace StardewModdingAPI.Framework /********* ** Properties *********/ + /// The registered mod data. + private readonly List Mods = new List(); + /// The friendly mod names treated as deprecation warning sources (assembly full name => mod name). private readonly IDictionary ModNamesByAssembly = new Dictionary(); @@ -19,11 +23,17 @@ namespace StardewModdingAPI.Framework ** Public methods *********/ /// Register a mod as a possible source of deprecation warnings. - /// The mod manifest. - /// The mod assembly. - public void Add(Manifest manifest, Assembly assembly) + /// The mod instance. + public void Add(IMod mod) + { + this.Mods.Add(mod); + this.ModNamesByAssembly[mod.GetType().Assembly.FullName] = mod.ModManifest.Name; + } + + /// Get all enabled mods. + public IEnumerable GetMods() { - this.ModNamesByAssembly[assembly.FullName] = manifest.Name; + return (from mod in this.Mods select mod); } /// Get the friendly name for the closest assembly registered as a source of deprecation warnings. -- cgit From 90f5233cc7d8202a89e1878e1183562fe5ba17b9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Dec 2016 11:27:48 -0500 Subject: add config setting to disable update checks (#202) --- src/StardewModdingAPI/Framework/UserSettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/UserSettings.cs b/src/StardewModdingAPI/Framework/UserSettings.cs index 199d19b3..6a4fb353 100644 --- a/src/StardewModdingAPI/Framework/UserSettings.cs +++ b/src/StardewModdingAPI/Framework/UserSettings.cs @@ -3,7 +3,13 @@ /// Contains user settings from SMAPI's JSON configuration file. internal class UserSettings { + /********* + ** Accessors + *********/ /// Whether to enable development features. public bool DeveloperMode { get; set; } + + /// Whether to check if a newer version of SMAPI is available on startup. + public bool CheckForUpdates { get; set; } = true; } } -- cgit From 3fd16a65f181c710fbbe872f36428176efee7ffb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Dec 2016 12:45:42 -0500 Subject: move models into namespace (#192) --- src/StardewModdingAPI/Framework/GitRelease.cs | 19 ------------------- src/StardewModdingAPI/Framework/Models/GitRelease.cs | 19 +++++++++++++++++++ .../Framework/Models/UserSettings.cs | 15 +++++++++++++++ src/StardewModdingAPI/Framework/UpdateHelper.cs | 1 + src/StardewModdingAPI/Framework/UserSettings.cs | 15 --------------- 5 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/GitRelease.cs create mode 100644 src/StardewModdingAPI/Framework/Models/GitRelease.cs create mode 100644 src/StardewModdingAPI/Framework/Models/UserSettings.cs delete mode 100644 src/StardewModdingAPI/Framework/UserSettings.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/GitRelease.cs b/src/StardewModdingAPI/Framework/GitRelease.cs deleted file mode 100644 index 0da57efd..00000000 --- a/src/StardewModdingAPI/Framework/GitRelease.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Framework -{ - /// Metadata about a GitHub release tag. - internal class GitRelease - { - /********* - ** Accessors - *********/ - /// The display name. - [JsonProperty("name")] - public string Name { get; set; } - - /// The semantic version string. - [JsonProperty("tag_name")] - public string Tag { get; set; } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/Models/GitRelease.cs b/src/StardewModdingAPI/Framework/Models/GitRelease.cs new file mode 100644 index 00000000..bc53468f --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/GitRelease.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace StardewModdingAPI.Framework.Models +{ + /// Metadata about a GitHub release tag. + internal class GitRelease + { + /********* + ** Accessors + *********/ + /// The display name. + [JsonProperty("name")] + public string Name { get; set; } + + /// The semantic version string. + [JsonProperty("tag_name")] + public string Tag { get; set; } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/Models/UserSettings.cs b/src/StardewModdingAPI/Framework/Models/UserSettings.cs new file mode 100644 index 00000000..a0074f77 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/UserSettings.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// Contains user settings from SMAPI's JSON configuration file. + internal class UserSettings + { + /********* + ** Accessors + *********/ + /// Whether to enable development features. + public bool DeveloperMode { get; set; } + + /// Whether to check if a newer version of SMAPI is available on startup. + public bool CheckForUpdates { get; set; } = true; + } +} diff --git a/src/StardewModdingAPI/Framework/UpdateHelper.cs b/src/StardewModdingAPI/Framework/UpdateHelper.cs index ddd1d840..e01e55c8 100644 --- a/src/StardewModdingAPI/Framework/UpdateHelper.cs +++ b/src/StardewModdingAPI/Framework/UpdateHelper.cs @@ -3,6 +3,7 @@ using System.Net; using System.Reflection; using System.Threading.Tasks; using Newtonsoft.Json; +using StardewModdingAPI.Framework.Models; namespace StardewModdingAPI.Framework { diff --git a/src/StardewModdingAPI/Framework/UserSettings.cs b/src/StardewModdingAPI/Framework/UserSettings.cs deleted file mode 100644 index 6a4fb353..00000000 --- a/src/StardewModdingAPI/Framework/UserSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace StardewModdingAPI.Framework -{ - /// Contains user settings from SMAPI's JSON configuration file. - internal class UserSettings - { - /********* - ** Accessors - *********/ - /// Whether to enable development features. - public bool DeveloperMode { get; set; } - - /// Whether to check if a newer version of SMAPI is available on startup. - public bool CheckForUpdates { get; set; } = true; - } -} -- cgit From fdae87d340e90793ed00fa1766baf9dbd5bec9b6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Dec 2016 12:47:12 -0500 Subject: skip mods known to be incompatible and display error with update links (#192) --- .../Framework/Models/IncompatibleMod.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs new file mode 100644 index 00000000..f3ee7d0b --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs @@ -0,0 +1,24 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// Contains abstract metadata about an incompatible mod. + internal class IncompatibleMod + { + /********* + ** Accessors + *********/ + /// The unique mod ID. + public string ID { get; set; } + + /// The mod name. + public string Name { get; set; } + + /// The most recent incompatible mod version. + public string Version { get; set; } + + /// The URL the user can check for an official updated version. + public string UpdateUrl { get; set; } + + /// The URL the user can check for an unofficial updated version. + public string UnofficialUpdateUrl { get; set; } + } +} \ No newline at end of file -- cgit From c7a08d08db3305a7cfd3a6438beda48b0791eaac Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Dec 2016 22:34:49 -0500 Subject: add support for unofficial updates which suffix the official version number with a pre-release label (#192) --- .../Framework/Models/IncompatibleMod.cs | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs index f3ee7d0b..9bf06552 100644 --- a/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs +++ b/src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace StardewModdingAPI.Framework.Models { /// Contains abstract metadata about an incompatible mod. @@ -20,5 +22,26 @@ namespace StardewModdingAPI.Framework.Models /// The URL the user can check for an unofficial updated version. public string UnofficialUpdateUrl { get; set; } + + /// A regular expression matching version strings to consider compatible, even if they technically precede . + public string ForceCompatibleVersion { get; set; } + + + /********* + ** Public methods + *********/ + /// Get whether the specified version is compatible according to this metadata. + /// The current version of the matching mod. + public bool IsCompatible(ISemanticVersion version) + { + ISemanticVersion incompatibleVersion = new SemanticVersion(this.Version); + + // allow newer versions + if (version.IsNewerThan(incompatibleVersion)) + return true; + + // allow versions matching override + return !string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase); + } } } \ No newline at end of file -- cgit