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 ++++++---------------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 40 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 +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 1a31b751..65083e67 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -162,6 +162,7 @@ + -- 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/Constants.cs | 8 +- .../Framework/AssemblyRewriting/CacheEntry.cs | 2 +- .../Framework/ModAssemblyLoader.cs | 4 +- src/StardewModdingAPI/IManifest.cs | 27 +++++ src/StardewModdingAPI/ISemanticVersion.cs | 35 ++++++ src/StardewModdingAPI/Manifest.cs | 60 ++++++---- src/StardewModdingAPI/Mod.cs | 15 ++- src/StardewModdingAPI/Program.cs | 22 ++-- src/StardewModdingAPI/SemanticVersion.cs | 130 +++++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 3 + src/StardewModdingAPI/Version.cs | 118 +++++++------------ 11 files changed, 311 insertions(+), 113 deletions(-) create mode 100644 src/StardewModdingAPI/IManifest.cs create mode 100644 src/StardewModdingAPI/ISemanticVersion.cs create mode 100644 src/StardewModdingAPI/SemanticVersion.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index f5b9e70a..57a89e76 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -26,7 +26,11 @@ namespace StardewModdingAPI ** Accessors *********/ /// SMAPI's current semantic version. - public static readonly Version Version = new Version(1, 4, 0, null); + [Obsolete("Use " + nameof(Constants) + "." + nameof(ApiVersion))] + public static readonly Version Version = (Version)Constants.ApiVersion; + + /// SMAPI's current semantic version. + public static ISemanticVersion ApiVersion => new Version(1, 4, 0, null, suppressDeprecationWarning: true); /// The minimum supported version of Stardew Valley. public const string MinimumGameVersion = "1.1"; @@ -56,7 +60,7 @@ namespace StardewModdingAPI public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); /// The title of the SMAPI console window. - public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.Version} - Mods Loaded: {Program.ModsLoaded}"; + public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}"; /// The directory path in which error logs should be stored. public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); 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); diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs new file mode 100644 index 00000000..3e4b7513 --- /dev/null +++ b/src/StardewModdingAPI/IManifest.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI +{ + /// A manifest which describes a mod for SMAPI. + public interface IManifest + { + /// The mod name. + string Name { get; set; } + + /// A brief description of the mod. + string Description { get; set; } + + /// The mod author's name. + string Author { get; } + + /// The mod version. + ISemanticVersion Version { get; set; } + + /// The minimum SMAPI version required by this mod, if any. + string MinimumApiVersion { get; set; } + + /// The unique mod ID. + string UniqueID { get; set; } + + /// The name of the DLL in the directory that has the method. + string EntryDll { get; set; } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/ISemanticVersion.cs b/src/StardewModdingAPI/ISemanticVersion.cs new file mode 100644 index 00000000..f50752fe --- /dev/null +++ b/src/StardewModdingAPI/ISemanticVersion.cs @@ -0,0 +1,35 @@ +using System; + +namespace StardewModdingAPI +{ + /// A semantic version with an optional release tag. + public interface ISemanticVersion : IComparable + { + /********* + ** Accessors + *********/ + /// The major version incremented for major API changes. + int MajorVersion { get; } + + /// The minor version incremented for backwards-compatible changes. + int MinorVersion { get; } + + /// The patch version for backwards-compatible bug fixes. + int PatchVersion { get; } + + /// An optional build tag. + string Build { get; } + + + /********* + ** Accessors + *********/ + /// Get whether this version is older than the specified version. + /// The version to compare with this instance. + bool IsOlderThan(ISemanticVersion other); + + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + bool IsNewerThan(ISemanticVersion other); + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs index bfc66c43..981ff023 100644 --- a/src/StardewModdingAPI/Manifest.cs +++ b/src/StardewModdingAPI/Manifest.cs @@ -3,21 +3,52 @@ using Newtonsoft.Json; namespace StardewModdingAPI { + /// Wraps so it can implement without breaking backwards compatibility. + [Obsolete("Use " + nameof(IManifest) + " or " + nameof(Mod) + "." + nameof(Mod.ModManifest) + " instead")] + internal class ManifestImpl : Manifest, IManifest + { + /// The mod version. + public new ISemanticVersion Version + { + get { return base.Version; } + set { base.Version = (Version)value; } + } + } + /// A manifest which describes a mod for SMAPI. public class Manifest { /********* ** Accessors *********/ - /// Whether the manifest defined the deprecated field. - [JsonIgnore] - internal bool UsedAuthourField { get; private set; } - /// The mod name. - public virtual string Name { get; set; } = ""; + public string Name { get; set; } + + /// A brief description of the mod. + public string Description { get; set; } /// The mod author's name. - public virtual string Author { get; set; } = ""; + public string Author { get; set; } + + /// The mod version. + public Version Version { get; set; } = new Version(0, 0, 0, "", suppressDeprecationWarning: true); + + /// The minimum SMAPI version required by this mod, if any. + public string MinimumApiVersion { get; set; } + + /// The name of the DLL in the directory that has the method. + public string EntryDll { get; set; } + + /// The unique mod ID. + public string UniqueID { get; set; } = Guid.NewGuid().ToString(); + + + /**** + ** Obsolete + ****/ + /// Whether the manifest defined the deprecated field. + [JsonIgnore] + internal bool UsedAuthourField { get; private set; } /// Obsolete. [Obsolete("Use " + nameof(Manifest) + "." + nameof(Manifest.Author) + ".")] @@ -31,23 +62,8 @@ namespace StardewModdingAPI } } - /// The mod version. - public virtual Version Version { get; set; } = new Version(0, 0, 0, ""); - - /// A brief description of the mod. - public virtual string Description { get; set; } = ""; - - /// The unique mod ID. - public virtual string UniqueID { get; set; } = Guid.NewGuid().ToString(); - /// Whether the mod uses per-save config files. [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")] - public virtual bool PerSaveConfigs { get; set; } - - /// The minimum SMAPI version required by this mod, if any. - public string MinimumApiVersion { get; set; } - - /// The name of the DLL in the directory that has the method. - public virtual string EntryDll { get; set; } = ""; + public bool PerSaveConfigs { get; set; } } } diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index 21551771..f0f876fa 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -24,7 +24,18 @@ namespace StardewModdingAPI public IMonitor Monitor { get; internal set; } /// The mod's manifest. - public Manifest Manifest { get; internal set; } + [Obsolete("Use " + nameof(Mod) + "." + nameof(ModManifest))] + public Manifest Manifest + { + get + { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Manifest)}", "1.5", DeprecationLevel.Notice); + return (Manifest)this.ModManifest; + } + } + + /// The mod's manifest. + public IManifest ModManifest { get; internal set; } /// The full path to the mod's directory on the disk. [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.DirectoryPath) + " instead")] @@ -94,7 +105,7 @@ namespace StardewModdingAPI Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings - if (!this.Manifest.PerSaveConfigs) + if (!((Manifest)this.Manifest).PerSaveConfigs) { this.Monitor.Log("Tried to fetch the per-save config folder, but this mod isn't configured to use per-save config files.", LogLevel.Error); return ""; diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 62b9dabd..7c9cdcc3 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -98,7 +98,7 @@ namespace StardewModdingAPI Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // add info header - Program.Monitor.Log($"SMAPI {Constants.Version} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); + Program.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); // load user settings { @@ -191,9 +191,9 @@ namespace StardewModdingAPI try { GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; - Version latestVersion = new Version(release.Tag); - if (latestVersion.IsNewerThan(Constants.Version)) - Program.Monitor.Log($"You can update SMAPI from version {Constants.Version} to {latestVersion}", LogLevel.Alert); + ISemanticVersion latestVersion = new SemanticVersion(release.Tag); + if (latestVersion.IsNewerThan(Constants.ApiVersion)) + Program.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); } catch (Exception ex) { @@ -212,7 +212,7 @@ namespace StardewModdingAPI Program.StardewAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath); Program.StardewProgramType = Program.StardewAssembly.GetType("StardewValley.Program", true); Program.StardewGameInfo = Program.StardewProgramType.GetField("gamePtr"); - Game1.version += $"-Z_MODDED | SMAPI {Constants.Version}"; + Game1.version += $"-Z_MODDED | SMAPI {Constants.ApiVersion}"; // add error interceptors #if SMAPI_FOR_WINDOWS @@ -335,7 +335,7 @@ namespace StardewModdingAPI string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; // read manifest - Manifest manifest; + ManifestImpl manifest; try { // read manifest text @@ -347,7 +347,7 @@ namespace StardewModdingAPI } // deserialise manifest - manifest = helper.ReadJsonFile("manifest.json"); + manifest = helper.ReadJsonFile("manifest.json"); if (manifest == null) { Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); @@ -374,8 +374,8 @@ namespace StardewModdingAPI { try { - Version minVersion = new Version(manifest.MinimumApiVersion); - if (minVersion.IsNewerThan(Constants.Version)) + ISemanticVersion minVersion = new SemanticVersion(manifest.MinimumApiVersion); + if (minVersion.IsNewerThan(Constants.ApiVersion)) { Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); continue; @@ -473,11 +473,11 @@ namespace StardewModdingAPI Program.ModRegistry.Add(manifest, modAssembly); // hook up mod - modEntry.Manifest = manifest; + modEntry.ModManifest = manifest; modEntry.Helper = helper; modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode }; modEntry.PathOnDisk = directory; - Program.Monitor.Log($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}", LogLevel.Info); + Program.Monitor.Log($"Loaded mod: {modEntry.ModManifest.Name} by {modEntry.ModManifest.Author}, v{modEntry.ModManifest.Version} | {modEntry.ModManifest.Description}", LogLevel.Info); Program.ModsLoaded += 1; modEntry.Entry(); // deprecated since 1.0 modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1 diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs new file mode 100644 index 00000000..b3d9ee4a --- /dev/null +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -0,0 +1,130 @@ +using System; +using System.Text.RegularExpressions; + +namespace StardewModdingAPI +{ + /// A semantic version with an optional release tag. + public class SemanticVersion : ISemanticVersion + { + /********* + ** Properties + *********/ + /// A regular expression matching a semantic version string. + /// Derived from https://github.com/maxhauser/semver. + private static readonly Regex Regex = new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); + + + /********* + ** Accessors + *********/ + /// The major version incremented for major API changes. + public int MajorVersion { get; set; } + + /// The minor version incremented for backwards-compatible changes. + public int MinorVersion { get; set; } + + /// The patch version for backwards-compatible bug fixes. + public int PatchVersion { get; set; } + + /// An optional build tag. + public string Build { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The major version incremented for major API changes. + /// The minor version incremented for backwards-compatible changes. + /// The patch version for backwards-compatible bug fixes. + /// An optional build tag. + public SemanticVersion(int major, int minor, int patch, string build = null) + { + this.MajorVersion = major; + this.MinorVersion = minor; + this.PatchVersion = patch; + this.Build = build; + } + + /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. + /// The version to compare with this instance. + public int CompareTo(ISemanticVersion other) + { + // compare version numbers + if (this.MajorVersion != other.MajorVersion) + return this.MajorVersion - other.MajorVersion; + if (this.MinorVersion != other.MinorVersion) + return this.MinorVersion - other.MinorVersion; + if (this.PatchVersion != other.PatchVersion) + return this.PatchVersion - other.PatchVersion; + + // stable version (without tag) supercedes prerelease (with tag) + bool curHasTag = !string.IsNullOrWhiteSpace(this.Build); + bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build); + if (!curHasTag && otherHasTag) + return 1; + if (curHasTag && !otherHasTag) + return -1; + + // else compare by string + return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); + } + + /// Get whether this version is older than the specified version. + /// The version to compare with this instance. + public bool IsOlderThan(ISemanticVersion other) + { + return this.CompareTo(other) < 0; + } + + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + public bool IsNewerThan(ISemanticVersion other) + { + return this.CompareTo(other) > 0; + } + + /// Get a string representation of the version. + public override string ToString() + { + // version + string result = this.PatchVersion != 0 + ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}" + : $"{this.MajorVersion}.{this.MinorVersion}"; + + // tag + string tag = this.GetNormalisedTag(this.Build); + if (tag != null) + result += $"-{tag}"; + return result; + } + + + /********* + ** Private methods + *********/ + /// Construct an instance. + /// The semantic version string. + internal SemanticVersion(string version) + { + var match = SemanticVersion.Regex.Match(version); + if (!match.Success) + throw new FormatException($"The input '{version}' is not a semantic version."); + + this.MajorVersion = int.Parse(match.Groups["major"].Value); + this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; + this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; + this.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.'); + } + + /// Get a normalised build tag. + /// The tag to normalise. + private string GetNormalisedTag(string tag) + { + tag = tag?.Trim().Trim('-', '.'); + if (string.IsNullOrWhiteSpace(tag) || tag == "0") + return null; + return tag; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 65083e67..875bc1f3 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -166,8 +166,10 @@ + + @@ -190,6 +192,7 @@ + diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs index 7c6d319c..9c13381a 100644 --- a/src/StardewModdingAPI/Version.cs +++ b/src/StardewModdingAPI/Version.cs @@ -1,21 +1,13 @@ using System; -using System.Text.RegularExpressions; using Newtonsoft.Json; using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// A semantic version with an optional release tag. - public struct Version : IComparable + [Obsolete("Use " + nameof(SemanticVersion) + " or " + nameof(Manifest) + "." + nameof(Manifest.Version) + " instead")] + public struct Version : ISemanticVersion { - /********* - ** Properties - *********/ - /// A regular expression matching a semantic version string. - /// Derived from https://github.com/maxhauser/semver. - private static readonly Regex Regex = new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); - - /********* ** Accessors *********/ @@ -39,7 +31,7 @@ namespace StardewModdingAPI get { Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0", DeprecationLevel.Notice); - return this.ToString(); + return this.GetSemanticVersion().ToString(); } } @@ -53,92 +45,72 @@ namespace StardewModdingAPI /// The patch version for backwards-compatible bug fixes. /// An optional build tag. public Version(int major, int minor, int patch, string build) + : this(major, minor, patch, build, suppressDeprecationWarning: false) + { } + + /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. + /// The version to compare with this instance. + public int CompareTo(Version other) { - this.MajorVersion = major; - this.MinorVersion = minor; - this.PatchVersion = patch; - this.Build = build; + return this.GetSemanticVersion().CompareTo(other); } - /// Construct an instance. - /// The semantic version string. - internal Version(string version) + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + [Obsolete("Use " + nameof(ISemanticVersion) + "." + nameof(ISemanticVersion.IsNewerThan) + " instead")] + public bool IsNewerThan(Version other) { - var match = Version.Regex.Match(version); - if (!match.Success) - throw new FormatException($"The input '{version}' is not a semantic version."); - - this.MajorVersion = int.Parse(match.Groups["major"].Value); - this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; - this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; - this.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.'); + return this.GetSemanticVersion().IsNewerThan(other); } - /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. - /// The version to compare with this instance. - public int CompareTo(Version other) + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. + /// An object to compare with this instance. + int IComparable.CompareTo(ISemanticVersion other) { - // compare version numbers - if (this.MajorVersion != other.MajorVersion) - return this.MajorVersion - other.MajorVersion; - if (this.MinorVersion != other.MinorVersion) - return this.MinorVersion - other.MinorVersion; - if (this.PatchVersion != other.PatchVersion) - return this.PatchVersion - other.PatchVersion; - - // stable version (without tag) supercedes prerelease (with tag) - bool curHasTag = !string.IsNullOrWhiteSpace(this.Build); - bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build); - if (!curHasTag && otherHasTag) - return 1; - if (curHasTag && !otherHasTag) - return -1; - - // else compare by string - return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); + return this.GetSemanticVersion().CompareTo(other); } /// Get whether this version is older than the specified version. /// The version to compare with this instance. - public bool IsOlderThan(Version other) + bool ISemanticVersion.IsOlderThan(ISemanticVersion other) { - return this.CompareTo(other) < 0; + return this.GetSemanticVersion().IsOlderThan(other); } /// Get whether this version is newer than the specified version. /// The version to compare with this instance. - public bool IsNewerThan(Version other) - { - return this.CompareTo(other) > 0; - } - - /// Get a string representation of the version. - public override string ToString() + bool ISemanticVersion.IsNewerThan(ISemanticVersion other) { - // version - string result = this.PatchVersion != 0 - ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}" - : $"{this.MajorVersion}.{this.MinorVersion}"; - - // tag - string tag = this.GetNormalisedTag(this.Build); - if (tag != null) - result += $"-{tag}"; - return result; + return this.GetSemanticVersion().IsNewerThan(other); } /********* ** Private methods *********/ - /// Get a normalised build tag. - /// The tag to normalise. - private string GetNormalisedTag(string tag) + /// Construct an instance. + /// The major version incremented for major API changes. + /// The minor version incremented for backwards-compatible changes. + /// The patch version for backwards-compatible bug fixes. + /// An optional build tag. + /// Whether to suppress the deprecation warning. + internal Version(int major, int minor, int patch, string build, bool suppressDeprecationWarning) + { + if (!suppressDeprecationWarning) + Program.DeprecationManager.Warn($"{nameof(Version)}", "1.5", DeprecationLevel.Notice); + + this.MajorVersion = major; + this.MinorVersion = minor; + this.PatchVersion = patch; + this.Build = build; + } + + /// Get the equivalent semantic version. + /// This is a hack so the struct can wrap without a mutable backing field, which would cause a due to recreating the struct value on each change. + private SemanticVersion GetSemanticVersion() { - tag = tag?.Trim().Trim('-', '.'); - if (string.IsNullOrWhiteSpace(tag) || tag == "0") - return null; - return tag; + return new SemanticVersion(this.MajorVersion, this.MinorVersion, this.PatchVersion, this.Build); } } } -- 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 ++++++-- src/StardewModdingAPI/IMod.cs | 26 +++++++++++ src/StardewModdingAPI/Mod.cs | 2 +- src/StardewModdingAPI/Program.cs | 60 ++++++++++++++++---------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 5 files changed, 80 insertions(+), 27 deletions(-) create mode 100644 src/StardewModdingAPI/IMod.cs (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. diff --git a/src/StardewModdingAPI/IMod.cs b/src/StardewModdingAPI/IMod.cs new file mode 100644 index 00000000..35ac7c0f --- /dev/null +++ b/src/StardewModdingAPI/IMod.cs @@ -0,0 +1,26 @@ +namespace StardewModdingAPI +{ + /// The implementation for a Stardew Valley mod. + public interface IMod + { + /********* + ** Accessors + *********/ + /// Provides simplified APIs for writing mods. + IModHelper Helper { get; } + + /// Writes messages to the console and log file. + IMonitor Monitor { get; } + + /// The mod's manifest. + IManifest ModManifest { get; } + + + /********* + ** Public methods + *********/ + /// The mod entry point, called after the mod is first loaded. + /// Provides simplified APIs for writing mods. + void Entry(IModHelper helper); + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index f0f876fa..d12a7e05 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -5,7 +5,7 @@ using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// The base class for a mod. - public class Mod + public class Mod : IMod { /********* ** Properties diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 8a87c15d..f2d7faa8 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -462,37 +462,53 @@ namespace StardewModdingAPI continue; } - // hook up mod + // get mod instance + Mod mod; try { + // get implementation TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod)); - Mod modEntry = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); - if (modEntry != null) + mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); + if (mod == null) { - // track mod - Program.ModRegistry.Add(manifest, modAssembly); - - // hook up mod - modEntry.ModManifest = manifest; - modEntry.Helper = helper; - modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode }; - modEntry.PathOnDisk = directory; - Program.Monitor.Log($"Loaded mod: {modEntry.ModManifest.Name} by {modEntry.ModManifest.Author}, v{modEntry.ModManifest.Version} | {modEntry.ModManifest.Description}", LogLevel.Info); - Program.ModsLoaded += 1; - modEntry.Entry(); // deprecated since 1.0 - modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1 - modEntry.Entry(modEntry.Helper); // deprecated since 1.1 - - // raise deprecation warning for old Entry() method - if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) - Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0", DeprecationLevel.Notice); - if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(ModHelper) })) - Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.1", DeprecationLevel.Notice); + Program.Monitor.Log($"{errorPrefix}: the mod's entry class could not be instantiated."); + continue; } + + // inject data + mod.ModManifest = manifest; + mod.Helper = helper; + mod.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode }; + mod.PathOnDisk = directory; + + // track mod + Program.ModRegistry.Add(mod); + Program.ModsLoaded += 1; + Program.Monitor.Log($"Loaded mod: {manifest.Name} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); } catch (Exception ex) { Program.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + continue; + } + + // call mod entry + try + { + // call entry methods + mod.Entry(); // deprecated since 1.0 + mod.Entry((ModHelper)mod.Helper); // deprecated since 1.1 + mod.Entry(mod.Helper); + + // raise deprecation warning for old Entry() methods + if (Program.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) + Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0", DeprecationLevel.Notice); + if (Program.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(ModHelper) })) + Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.1", DeprecationLevel.Notice); + } + catch (Exception ex) + { + Program.Monitor.Log($"The {manifest.Name} mod failed on entry initialisation. It will still be loaded, but may not function correctly.\n{ex.GetLogSummary()}", LogLevel.Warn); } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index cbcd9964..9dec9881 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -167,6 +167,7 @@ + -- 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) --- release-notes.md | 1 + src/StardewModdingAPI/Framework/UserSettings.cs | 6 ++++++ src/StardewModdingAPI/Program.cs | 7 +++++-- src/StardewModdingAPI/StardewModdingAPI.config.json | 3 ++- 4 files changed, 14 insertions(+), 3 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/release-notes.md b/release-notes.md index 04a9cc04..804d78ba 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/stable...develop). For players: + * Added option to disable update checks. * Fixed error when a mod uses the new reflection API on a missing field or method. For developers: 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; } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index e316dbd8..9ecb91e3 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -118,8 +118,10 @@ namespace StardewModdingAPI if (Program.Settings.DeveloperMode) { Program.Monitor.ShowTraceInConsole = true; - Program.Monitor.Log($"SMAPI is running in developer mode. The console may be much more verbose. You can disable developer mode by editing or deleting {Constants.ApiConfigPath}.", LogLevel.Alert); + Program.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); } + if (!Program.Settings.CheckForUpdates) + Program.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by editing or deleting {Constants.ApiConfigPath}.", LogLevel.Warn); // initialise legacy log Log.Monitor = new Monitor("legacy mod", Program.LogFile) { ShowTraceInConsole = Program.Settings.DeveloperMode }; @@ -148,7 +150,8 @@ namespace StardewModdingAPI } // check for update when game loads - GameEvents.GameLoaded += (sender, e) => Program.CheckForUpdateAsync(); + if (Program.Settings.CheckForUpdates) + GameEvents.GameLoaded += (sender, e) => Program.CheckForUpdateAsync(); // launch game Program.StartGame(); diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 771c28e2..2abaf73a 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -1,3 +1,4 @@ { - "DeveloperMode": true + "DeveloperMode": true, + "CheckForUpdates": 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 --------------- src/StardewModdingAPI/Program.cs | 1 + src/StardewModdingAPI/StardewModdingAPI.csproj | 4 ++-- 7 files changed, 38 insertions(+), 36 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; - } -} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 9ecb91e3..cc3cb2bc 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -14,6 +14,7 @@ using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.AssemblyRewriting; +using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Inheritance; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 9dec9881..0c6697cb 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -174,8 +174,8 @@ - - + + -- 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) --- README.md | 2 ++ .../InteractiveInstaller.cs | 1 + .../StardewModdingAPI.Installer.csproj | 2 ++ src/StardewModdingAPI/Constants.cs | 3 +++ .../Framework/Models/IncompatibleMod.cs | 24 ++++++++++++++++++++++ src/StardewModdingAPI/Manifest.cs | 2 +- src/StardewModdingAPI/Program.cs | 22 ++++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 5 +++++ src/StardewModdingAPI/StardewModdingAPI.data.json | 16 +++++++++++++++ 9 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI/Framework/Models/IncompatibleMod.cs create mode 100644 src/StardewModdingAPI/StardewModdingAPI.data.json (limited to 'src/StardewModdingAPI/Framework') diff --git a/README.md b/README.md index c4bcd8d4..eb21f379 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ directory containing `src`). Newtonsoft.Json.dll StardewModdingAPI StardewModdingAPI.config.json + StardewModdingAPI.data.json StardewModdingAPI.exe StardewModdingAPI.exe.mdb StardewModdingAPI.AssemblyRewriters.dll @@ -91,6 +92,7 @@ directory containing `src`). Mono.Cecil.Rocks.dll Newtonsoft.Json.dll StardewModdingAPI.config.json + StardewModdingAPI.data.json StardewModdingAPI.exe StardewModdingAPI.pdb StardewModdingAPI.xml diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index ce6c83d9..5f89caf2 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -56,6 +56,7 @@ namespace StardewModdingApi.Installer // common "StardewModdingAPI.exe", "StardewModdingAPI.config.json", + "StardewModdingAPI.data.json", "StardewModdingAPI.AssemblyRewriters.dll", "steam_appid.txt", diff --git a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj index c9dedd45..4e4872b6 100644 --- a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj @@ -74,6 +74,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 6df9b76c..4f7a0c6e 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -71,6 +71,9 @@ namespace StardewModdingAPI /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); + /// The file path for the SMAPI data file containing metadata about known mods. + internal static string ApiModMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.data.json"); + /********* ** Protected methods 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 diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs index 981ff023..018b31ae 100644 --- a/src/StardewModdingAPI/Manifest.cs +++ b/src/StardewModdingAPI/Manifest.cs @@ -40,7 +40,7 @@ namespace StardewModdingAPI public string EntryDll { get; set; } /// The unique mod ID. - public string UniqueID { get; set; } = Guid.NewGuid().ToString(); + public string UniqueID { get; set; } /**** diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index cc3cb2bc..7de22ee9 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -315,6 +315,11 @@ namespace StardewModdingAPI ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CacheDirName, Program.TargetPlatform, Program.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); + // get known incompatible mods + IDictionary incompatibleMods = File.Exists(Constants.ApiModMetadataPath) + ? JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiModMetadataPath)).ToDictionary(p => p.ID, p => p) + : new Dictionary(0); + // load mods foreach (string directory in Directory.GetDirectories(Program.ModPath)) { @@ -376,6 +381,23 @@ namespace StardewModdingAPI continue; } + // validate known incompatible mods + IncompatibleMod incompatible; + if (incompatibleMods.TryGetValue(manifest.UniqueID ?? $"{manifest.Name}|{manifest.Author}|{manifest.EntryDll}", out incompatible)) + { + if (!manifest.Version.IsNewerThan(new SemanticVersion(incompatible.Version))) + { + string warning = $"Skipped {incompatible.Name} ≤v{incompatible.Version} because this version is not compatible with the latest version of the game. Please check for a newer version of the mod here:"; + if (!string.IsNullOrWhiteSpace(incompatible.UpdateUrl)) + warning += $"{Environment.NewLine}- official mod: {incompatible.UpdateUrl}"; + if (!string.IsNullOrWhiteSpace(incompatible.UnofficialUpdateUrl)) + warning += $"{Environment.NewLine}- unofficial update: {incompatible.UnofficialUpdateUrl}"; + + Program.Monitor.Log(warning, LogLevel.Error); + continue; + } + } + // validate version if (!string.IsNullOrWhiteSpace(manifest.MinimumApiVersion)) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 0c6697cb..07b1ff5e 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -161,6 +161,7 @@ + @@ -206,6 +207,9 @@ Always + + Always + Always @@ -253,6 +257,7 @@ + diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json new file mode 100644 index 00000000..91321c69 --- /dev/null +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -0,0 +1,16 @@ +/* + + +This file contains advanced metadata for SMAPI. You shouldn't change this file. + + +*/ +[ + { + "ID": "CJBCheatsMenu", + "Name": "CJB Cheats Menu", + "Version": "1.12", + "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031" + } +] -- 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 ++++++++++++++++++++++ src/StardewModdingAPI/Program.cs | 18 ++++++++--------- src/StardewModdingAPI/StardewModdingAPI.data.json | 12 +++++++---- 3 files changed, 40 insertions(+), 13 deletions(-) (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 diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index c3bd1646..0c232b1d 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -384,23 +384,23 @@ namespace StardewModdingAPI } // validate known incompatible mods - IncompatibleMod incompatible; - if (incompatibleMods.TryGetValue(manifest.UniqueID ?? $"{manifest.Name}|{manifest.Author}|{manifest.EntryDll}", out incompatible)) + IncompatibleMod compatibility; + if (incompatibleMods.TryGetValue(manifest.UniqueID ?? $"{manifest.Name}|{manifest.Author}|{manifest.EntryDll}", out compatibility)) { - if (!manifest.Version.IsNewerThan(new SemanticVersion(incompatible.Version))) + if (!compatibility.IsCompatible(manifest.Version)) { - string warning = $"Skipped {incompatible.Name} ≤v{incompatible.Version} because this version is not compatible with the latest version of the game. Please check for a newer version of the mod here:"; - if (!string.IsNullOrWhiteSpace(incompatible.UpdateUrl)) - warning += $"{Environment.NewLine}- official mod: {incompatible.UpdateUrl}"; - if (!string.IsNullOrWhiteSpace(incompatible.UnofficialUpdateUrl)) - warning += $"{Environment.NewLine}- unofficial update: {incompatible.UnofficialUpdateUrl}"; + string warning = $"Skipped {compatibility.Name} ≤v{compatibility.Version} because this version is not compatible with the latest version of the game. Please check for a newer version of the mod here:"; + if (!string.IsNullOrWhiteSpace(compatibility.UpdateUrl)) + warning += $"{Environment.NewLine}- official mod: {compatibility.UpdateUrl}"; + if (!string.IsNullOrWhiteSpace(compatibility.UnofficialUpdateUrl)) + warning += $"{Environment.NewLine}- unofficial update: {compatibility.UnofficialUpdateUrl}"; Program.Monitor.Log(warning, LogLevel.Error); continue; } } - // validate version + // validate SMAPI version if (!string.IsNullOrWhiteSpace(manifest.MinimumApiVersion)) { try diff --git a/src/StardewModdingAPI/StardewModdingAPI.data.json b/src/StardewModdingAPI/StardewModdingAPI.data.json index 2f1d67e8..49b45018 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.data.json +++ b/src/StardewModdingAPI/StardewModdingAPI.data.json @@ -11,27 +11,31 @@ This file contains advanced metadata for SMAPI. You shouldn't change this file. "Name": "Better Sprinklers", "Version": "2.1", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/41", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031" + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^2.1-EntoPatch" }, { "ID": "SPDChestLabel", "Name": "Chest Label System", "Version": "1.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/242", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031" + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^1.5-EntoPatch" }, { "ID": "CJBCheatsMenu", "Name": "CJB Cheats Menu", "Version": "1.12", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/4", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031" + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^1.12-EntoPatch" }, { "ID": "CJBItemSpawner", "Name": "CJB Item Spawner", "Version": "1.5", "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/93", - "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031" + "UnofficialUpdateUrl": "http://community.playstarbound.com/threads/125031", + "ForceCompatibleVersion": "^1.5-EntoPatch" } ] -- cgit