From 31a49b83c2fda9f3990e953aeaefc2b415333f31 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 4 Jan 2019 18:32:12 -0500 Subject: update NuGet packages --- src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj index 46d38f17..a386fc5e 100644 --- a/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj @@ -14,8 +14,8 @@ - - + + -- cgit From bad2ac2a29b8775be97133e4c4cfb67a4a7406ee Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 24 Feb 2019 19:56:08 -0500 Subject: remove deprecated APIs (#606) --- src/SMAPI.Toolkit/SemanticVersion.cs | 19 +------------------ .../Converters/SemanticVersionConverter.cs | 14 +------------- 2 files changed, 2 insertions(+), 31 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs index ba9ca6c6..78b4c007 100644 --- a/src/SMAPI.Toolkit/SemanticVersion.cs +++ b/src/SMAPI.Toolkit/SemanticVersion.cs @@ -42,15 +42,6 @@ namespace StardewModdingAPI.Toolkit /// An optional prerelease tag. public string PrereleaseTag { get; } -#if !SMAPI_3_0_STRICT - /// An optional prerelease tag. - [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")] - public string Build => this.PrereleaseTag; - - /// Whether the version was parsed from the legacy object format. - public bool IsLegacyFormat { get; } -#endif - /********* ** Public methods @@ -60,20 +51,12 @@ namespace StardewModdingAPI.Toolkit /// The minor version incremented for backwards-compatible changes. /// The patch version for backwards-compatible fixes. /// An optional prerelease tag. - /// Whether the version was parsed from the legacy object format. - public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null -#if !SMAPI_3_0_STRICT - , bool isLegacyFormat = false -#endif - ) + public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null) { this.MajorVersion = major; this.MinorVersion = minor; this.PatchVersion = patch; this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag); -#if !SMAPI_3_0_STRICT - this.IsLegacyFormat = isLegacyFormat; -#endif this.AssertValid(); } diff --git a/src/SMAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs index aca06849..c50de4db 100644 --- a/src/SMAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs @@ -67,20 +67,8 @@ namespace StardewModdingAPI.Toolkit.Serialisation.Converters int minor = obj.ValueIgnoreCase(nameof(ISemanticVersion.MinorVersion)); int patch = obj.ValueIgnoreCase(nameof(ISemanticVersion.PatchVersion)); string prereleaseTag = obj.ValueIgnoreCase(nameof(ISemanticVersion.PrereleaseTag)); -#if !SMAPI_3_0_STRICT - if (string.IsNullOrWhiteSpace(prereleaseTag)) - { - prereleaseTag = obj.ValueIgnoreCase("Build"); - if (prereleaseTag == "0") - prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation - } -#endif - return new SemanticVersion(major, minor, patch, prereleaseTag -#if !SMAPI_3_0_STRICT - , isLegacyFormat: true -#endif - ); + return new SemanticVersion(major, minor, patch, prereleaseTag); } /// Read a JSON string. -- cgit From abffdc2dab2a1c03904427b251acac9d800e0912 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 Apr 2019 23:45:34 -0400 Subject: simplify project names --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 28 ++++++++++++++++++++++ src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj | 28 ---------------------- 2 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 src/SMAPI.Toolkit/SMAPI.Toolkit.csproj delete mode 100644 src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj new file mode 100644 index 00000000..8a029e62 --- /dev/null +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -0,0 +1,28 @@ + + + + net4.5;netstandard2.0 + false + ..\..\bin\$(Configuration)\SMAPI.Toolkit + ..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\SMAPI.Toolkit.xml + latest + x86 + + + + + + + + + + + + + + + + + + + diff --git a/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj deleted file mode 100644 index a386fc5e..00000000 --- a/src/SMAPI.Toolkit/StardewModdingAPI.Toolkit.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net4.5;netstandard2.0 - false - ..\..\bin\$(Configuration)\SMAPI.Toolkit - ..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\StardewModdingAPI.Toolkit.xml - latest - x86 - - - - - - - - - - - - - - - - - - - -- cgit From d74b710833edd19d2df2c0847a033078fa71a06e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 May 2019 23:31:42 -0400 Subject: add mod type to mod scanner result --- src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs | 7 ++++++- .../Framework/ModScanning/ModScanner.cs | 20 +++++++++++++++----- src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs index bb467b36..adfee527 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -18,6 +18,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// The folder containing the mod's manifest.json. public DirectoryInfo Directory { get; } + /// The mod type. + public ModType Type { get; } + /// The mod manifest. public Manifest Manifest { get; } @@ -34,13 +37,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// Construct an instance. /// The root folder containing mods. /// The folder containing the mod's manifest.json. + /// The mod type. /// The mod manifest. /// The error which occurred parsing the manifest, if any. /// Whether the mod should be loaded by default. This should be false if it was found within a folder whose name starts with a dot. - public ModFolder(DirectoryInfo root, DirectoryInfo directory, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true) + public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true) { // save info this.Directory = directory; + this.Type = type; this.Manifest = manifest; this.ManifestParseError = manifestParseError; this.ShouldBeLoaded = shouldBeLoaded; diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 0ab73d56..507e7be2 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -65,10 +65,10 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray(); if (!files.Any()) - return new ModFolder(root, searchFolder, null, "it's an empty folder."); + return new ModFolder(root, searchFolder, ModType.Invalid, null, "it's an empty folder."); if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension))) - return new ModFolder(root, searchFolder, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); - return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json."); + return new ModFolder(root, searchFolder, ModType.Xnb, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); + return new ModFolder(root, searchFolder, ModType.Invalid, null, "it contains files, but none of them are manifest.json."); } // read mod info @@ -98,7 +98,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning manifest.Author = this.StripNewlines(manifest.Author); } - return new ModFolder(root, manifestFile.Directory, manifest, manifestError); + // get mod type + ModType type = ModType.Invalid; + if (manifest != null) + { + type = !string.IsNullOrWhiteSpace(manifest.ContentPackFor?.UniqueID) + ? ModType.ContentPack + : ModType.Smapi; + } + + // build result + return new ModFolder(root, manifestFile.Directory, type, manifest, manifestError); } @@ -112,7 +122,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { // skip if (folder.FullName != root.FullName && folder.Name.StartsWith(".")) - yield return new ModFolder(root, folder, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false); + yield return new ModFolder(root, folder, ModType.Invalid, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false); // recurse into subfolders else if (this.IsModSearchFolder(root, folder)) diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs new file mode 100644 index 00000000..2ceb9e40 --- /dev/null +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Toolkit.Framework.ModScanning +{ + /// A general mod type. + public enum ModType + { + /// The mod is invalid and its type could not be determined. + Invalid, + + /// A mod which uses SMAPI directly. + Smapi, + + /// A mod which contains files loaded by a SMAPI mod. + ContentPack, + + /// A legacy mod which replaces game files directly. + Xnb + } +} -- cgit From 30cc7ac9161e4cea91c3b566a7edb5e438af98cb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 May 2019 23:45:50 -0400 Subject: consolidate XNB mods when scanning mods --- .../Framework/ModScanning/ModScanner.cs | 27 ++++++++++++++++++---- src/SMAPI.Toolkit/ModToolkit.cs | 9 ++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 507e7be2..ae0f292f 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -52,6 +52,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning return this.GetModFolders(root, root); } + /// Extract information about all mods in the given folder. + /// The root folder containing mods. Only the will be searched, but this field allows it to be treated as a potential mod folder of its own. + /// The mod path to search. + // /// If the folder contains multiple XNB mods, treat them as subfolders of a single mod. This is useful when reading a single mod archive, as opposed to a mods folder. + public IEnumerable GetModFolders(string rootPath, string modPath) + { + return this.GetModFolders(root: new DirectoryInfo(rootPath), folder: new DirectoryInfo(modPath)); + } + /// Extract information from a mod folder. /// The root folder containing mods. /// The folder to search for a mod. @@ -120,17 +129,25 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// The folder to search for mods. public IEnumerable GetModFolders(DirectoryInfo root, DirectoryInfo folder) { + bool isRoot = folder.FullName == root.FullName; + // skip - if (folder.FullName != root.FullName && folder.Name.StartsWith(".")) + if (!isRoot && folder.Name.StartsWith(".")) yield return new ModFolder(root, folder, ModType.Invalid, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false); - // recurse into subfolders + // find mods in subfolders else if (this.IsModSearchFolder(root, folder)) { - foreach (DirectoryInfo subfolder in folder.EnumerateDirectories()) + ModFolder[] subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub)).ToArray(); + if (!isRoot && subfolders.Length > 1 && subfolders.All(p => p.Type == ModType.Xnb)) + { + // if this isn't the root, and all subfolders are XNB mods, treat the whole folder as one XNB mod + yield return new ModFolder(folder, folder, ModType.Xnb, null, subfolders[0].ManifestParseError); + } + else { - foreach (ModFolder match in this.GetModFolders(root, subfolder)) - yield return match; + foreach (ModFolder subfolder in subfolders) + yield return subfolder; } } diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 1b53e59e..f1da62a2 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -69,6 +69,15 @@ namespace StardewModdingAPI.Toolkit return new ModScanner(this.JsonHelper).GetModFolders(rootPath); } + /// Extract information about all mods in the given folder. + /// The root folder containing mods. Only the will be searched, but this field allows it to be treated as a potential mod folder of its own. + /// The mod path to search. + // /// If the folder contains multiple XNB mods, treat them as subfolders of a single mod. This is useful when reading a single mod archive, as opposed to a mods folder. + public IEnumerable GetModFolders(string rootPath, string modPath) + { + return new ModScanner(this.JsonHelper).GetModFolders(rootPath, modPath); + } + /// Get an update URL for an update key (if valid). /// The update key. public string GetUpdateUrl(string updateKey) -- cgit From 83ae036e09aab6830fcf83ae3065628d4ab91143 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 May 2019 13:53:34 -0400 Subject: improve XNB mod and ignore file matching --- .../Framework/ModScanning/ModFolder.cs | 20 ++- .../Framework/ModScanning/ModParseError.cs | 24 ++++ .../Framework/ModScanning/ModScanner.cs | 141 ++++++++++++++++----- src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs | 3 + 4 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 src/SMAPI.Toolkit/Framework/ModScanning/ModParseError.cs (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs index adfee527..4ce17c66 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -25,30 +25,38 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning public Manifest Manifest { get; } /// The error which occurred parsing the manifest, if any. - public string ManifestParseError { get; } + public ModParseError ManifestParseError { get; set; } - /// Whether the mod should be loaded by default. This is false if it was found within a folder whose name starts with a dot. - public bool ShouldBeLoaded { get; } + /// A human-readable message for the , if any. + public string ManifestParseErrorText { get; set; } /********* ** Public methods *********/ + /// Construct an instance. + /// The root folder containing mods. + /// The folder containing the mod's manifest.json. + /// The mod type. + /// The mod manifest. + public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest) + : this(root, directory, type, manifest, ModParseError.None, null) { } + /// Construct an instance. /// The root folder containing mods. /// The folder containing the mod's manifest.json. /// The mod type. /// The mod manifest. /// The error which occurred parsing the manifest, if any. - /// Whether the mod should be loaded by default. This should be false if it was found within a folder whose name starts with a dot. - public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true) + /// A human-readable message for the , if any. + public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, ModParseError manifestParseError, string manifestParseErrorText) { // save info this.Directory = directory; this.Type = type; this.Manifest = manifest; this.ManifestParseError = manifestParseError; - this.ShouldBeLoaded = shouldBeLoaded; + this.ManifestParseErrorText = manifestParseErrorText; // set display name this.DisplayName = manifest?.Name; diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModParseError.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModParseError.cs new file mode 100644 index 00000000..b10510ff --- /dev/null +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModParseError.cs @@ -0,0 +1,24 @@ +namespace StardewModdingAPI.Toolkit.Framework.ModScanning +{ + /// Indicates why a mod could not be parsed. + public enum ModParseError + { + /// No parse error. + None, + + /// The folder is empty or contains only ignored files. + EmptyFolder, + + /// The folder is ignored by convention. + IgnoredFolder, + + /// The mod's manifest.json could not be parsed. + ManifestInvalid, + + /// The folder contains non-ignored and non-XNB files, but none of them are manifest.json. + ManifestMissing, + + /// The folder is an XNB mod, which can't be loaded through SMAPI. + XnbMod + } +} diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index ae0f292f..54cb2b8b 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Serialisation.Models; @@ -17,20 +18,32 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning private readonly JsonHelper JsonHelper; /// A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod. - private readonly HashSet IgnoreFilesystemEntries = new HashSet(StringComparer.InvariantCultureIgnoreCase) + private readonly HashSet IgnoreFilesystemEntries = new HashSet { - ".DS_Store", - "mcs", - "Thumbs.db" + // OS metadata files + new Regex(@"^__folder_managed_by_vortex$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Vortex mod manager + new Regex(@"^(?:__MACOSX|\._\.DS_Store|\.DS_Store|mcs)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // MacOS + new Regex(@"^(?:desktop\.ini|Thumbs\.db)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows + new Regex(@"\.(?:url|lnk)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows shortcut files + + // other + new Regex(@"\.(?:bmp|gif|jpeg|jpg|png|psd|tif)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // image files + new Regex(@"\.(?:md|rtf|txt)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // text files + new Regex(@"\.(?:backup|bak|old)$", RegexOptions.Compiled | RegexOptions.IgnoreCase) // backup file }; - /// The extensions for files which an XNB mod may contain. If a mod contains *only* these file extensions, it should be considered an XNB mod. + /// The extensions for files which an XNB mod may contain. If a mod doesn't have a manifest.json and contains *only* these file extensions, it should be considered an XNB mod. private readonly HashSet PotentialXnbModExtensions = new HashSet(StringComparer.InvariantCultureIgnoreCase) { - ".md", - ".png", - ".txt", - ".xnb" + // XNB files + ".xgs", + ".xnb", + ".xsb", + ".xwb", + + // unpacking artifacts + ".json", + ".yaml" }; @@ -72,30 +85,36 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning // set appropriate invalid-mod error if (manifestFile == null) { - FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray(); + FileInfo[] files = this.RecursivelyGetRelevantFiles(searchFolder).ToArray(); if (!files.Any()) - return new ModFolder(root, searchFolder, ModType.Invalid, null, "it's an empty folder."); - if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension))) - return new ModFolder(root, searchFolder, ModType.Xnb, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); - return new ModFolder(root, searchFolder, ModType.Invalid, null, "it contains files, but none of them are manifest.json."); + return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyFolder, "it's an empty folder."); + if (files.All(this.IsPotentialXnbFile)) + return new ModFolder(root, searchFolder, ModType.Xnb, null, ModParseError.XnbMod, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); + return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.ManifestMissing, "it contains files, but none of them are manifest.json."); } // read mod info Manifest manifest = null; - string manifestError = null; + ModParseError error = ModParseError.None; + string errorText = null; { try { if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest) || manifest == null) - manifestError = "its manifest is invalid."; + { + error = ModParseError.ManifestInvalid; + errorText = "its manifest is invalid."; + } } catch (SParseException ex) { - manifestError = $"parsing its manifest failed: {ex.Message}"; + error = ModParseError.ManifestInvalid; + errorText = $"parsing its manifest failed: {ex.Message}"; } catch (Exception ex) { - manifestError = $"parsing its manifest failed:\n{ex}"; + error = ModParseError.ManifestInvalid; + errorText = $"parsing its manifest failed:\n{ex}"; } } @@ -117,7 +136,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } // build result - return new ModFolder(root, manifestFile.Directory, type, manifest, manifestError); + return new ModFolder(root, manifestFile.Directory, type, manifest, error, errorText); } @@ -127,28 +146,30 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// Recursively extract information about all mods in the given folder. /// The root mod folder. /// The folder to search for mods. - public IEnumerable GetModFolders(DirectoryInfo root, DirectoryInfo folder) + private IEnumerable GetModFolders(DirectoryInfo root, DirectoryInfo folder) { bool isRoot = folder.FullName == root.FullName; // skip - if (!isRoot && folder.Name.StartsWith(".")) - yield return new ModFolder(root, folder, ModType.Invalid, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false); - - // find mods in subfolders - else if (this.IsModSearchFolder(root, folder)) + if (!isRoot) { - ModFolder[] subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub)).ToArray(); - if (!isRoot && subfolders.Length > 1 && subfolders.All(p => p.Type == ModType.Xnb)) + if (folder.Name.StartsWith(".")) { - // if this isn't the root, and all subfolders are XNB mods, treat the whole folder as one XNB mod - yield return new ModFolder(folder, folder, ModType.Xnb, null, subfolders[0].ManifestParseError); - } - else - { - foreach (ModFolder subfolder in subfolders) - yield return subfolder; + yield return new ModFolder(root, folder, ModType.Ignored, null, ModParseError.IgnoredFolder, "ignored folder because its name starts with a dot."); + yield break; } + if (!this.IsRelevant(folder)) + yield break; + } + + // find mods in subfolders + if (this.IsModSearchFolder(root, folder)) + { + IEnumerable subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub)); + if (!isRoot) + subfolders = this.TryConsolidate(root, folder, subfolders.ToArray()); + foreach (ModFolder subfolder in subfolders) + yield return subfolder; } // treat as mod folder @@ -156,6 +177,26 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning yield return this.ReadFolder(root, folder); } + /// Consolidate adjacent folders into one mod folder, if possible. + /// The folder containing both parent and subfolders. + /// The parent folder to consolidate, if possible. + /// The subfolders to consolidate, if possible. + private IEnumerable TryConsolidate(DirectoryInfo root, DirectoryInfo parentFolder, ModFolder[] subfolders) + { + if (subfolders.Length > 1) + { + // a collection of empty folders + if (subfolders.All(p => p.ManifestParseError == ModParseError.EmptyFolder)) + return new[] { new ModFolder(root, parentFolder, ModType.Invalid, null, ModParseError.EmptyFolder, subfolders[0].ManifestParseErrorText) }; + + // an XNB mod + if (subfolders.All(p => p.Type == ModType.Xnb || p.ManifestParseError == ModParseError.EmptyFolder)) + return new[] { new ModFolder(root, parentFolder, ModType.Xnb, null, ModParseError.XnbMod, subfolders[0].ManifestParseErrorText) }; + } + + return subfolders; + } + /// Find the manifest for a mod folder. /// The folder to search. private FileInfo FindManifest(DirectoryInfo folder) @@ -193,11 +234,41 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning return subfolders.Any() && !files.Any(); } + /// Recursively get all relevant files in a folder based on the result of . + /// The root folder to search. + private IEnumerable RecursivelyGetRelevantFiles(DirectoryInfo folder) + { + foreach (FileSystemInfo entry in folder.GetFileSystemInfos()) + { + if (!this.IsRelevant(entry)) + continue; + + if (entry is FileInfo file) + yield return file; + + if (entry is DirectoryInfo subfolder) + { + foreach (FileInfo subfolderFile in this.RecursivelyGetRelevantFiles(subfolder)) + yield return subfolderFile; + } + } + } + /// Get whether a file or folder is relevant when deciding how to process a mod folder. /// The file or folder. private bool IsRelevant(FileSystemInfo entry) { - return !this.IgnoreFilesystemEntries.Contains(entry.Name); + return !this.IgnoreFilesystemEntries.Any(p => p.IsMatch(entry.Name)); + } + + /// Get whether a file is potentially part of an XNB mod. + /// The file. + private bool IsPotentialXnbFile(FileInfo entry) + { + if (!this.IsRelevant(entry)) + return true; + + return this.PotentialXnbModExtensions.Contains(entry.Extension); // use EndsWith to handle cases like image..png } /// Strip newlines from a string. diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs index 2ceb9e40..bc86edb6 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModType.cs @@ -6,6 +6,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// The mod is invalid and its type could not be determined. Invalid, + /// The folder is ignored by convention. + Ignored, + /// A mod which uses SMAPI directly. Smapi, -- cgit From 2be3b7fb01045d274cdd77633ffbb690c5fb2f6d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 May 2019 13:53:49 -0400 Subject: fix default toolkit namespace --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 1 + 1 file changed, 1 insertion(+) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 8a029e62..2c6493da 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -7,6 +7,7 @@ ..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\SMAPI.Toolkit.xml latest x86 + StardewModdingAPI.Toolkit -- cgit From 46a0dd6236a39175e85c11eac44a710db1c07847 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Jun 2019 14:49:05 -0400 Subject: move environment utility into toolkit for reuse --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 1 + src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs | 113 ++++++++++++++++++++++ src/SMAPI.Toolkit/Utilities/Platform.cs | 15 +++ 3 files changed, 129 insertions(+) create mode 100644 src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs create mode 100644 src/SMAPI.Toolkit/Utilities/Platform.cs (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 2c6493da..2be84316 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -18,6 +18,7 @@ + diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs new file mode 100644 index 00000000..4f67c00e --- /dev/null +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -0,0 +1,113 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +#if SMAPI_FOR_WINDOWS +using System.Management; +#endif +using System.Runtime.InteropServices; + +namespace StardewModdingAPI.Toolkit.Utilities +{ + /// Provides methods for fetching environment information. + public static class EnvironmentUtility + { + /********* + ** Fields + *********/ + /// Get the OS name from the system uname command. + /// The buffer to fill with the resulting string. + [DllImport("libc")] + static extern int uname(IntPtr buffer); + + + /********* + ** Public methods + *********/ + /// Detect the current OS. + public static Platform DetectPlatform() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + return Platform.Mac; + + case PlatformID.Unix: + return EnvironmentUtility.IsRunningMac() + ? Platform.Mac + : Platform.Linux; + + default: + return Platform.Windows; + } + } + + + /// Get the human-readable OS name and version. + /// The current platform. + [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] + public static string GetFriendlyPlatformName(Platform platform) + { +#if SMAPI_FOR_WINDOWS + try + { + return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") + .Get() + .Cast() + .Select(entry => entry.GetPropertyValue("Caption").ToString()) + .FirstOrDefault(); + } + catch { } +#endif + return (platform == Platform.Mac ? "MacOS " : "") + Environment.OSVersion; + } + + /// Get the name of the Stardew Valley executable. + /// The current platform. + public static string GetExecutableName(Platform platform) + { + return platform == Platform.Windows + ? "Stardew Valley.exe" + : "StardewValley.exe"; + } + + /// Get whether the platform uses Mono. + /// The current platform. + public static bool IsMono(this Platform platform) + { + return platform == Platform.Linux || platform == Platform.Mac; + } + + + /********* + ** Private methods + *********/ + /// Detect whether the code is running on Mac. + /// + /// This code is derived from the Mono project (see System.Windows.Forms/System.Windows.Forms/XplatUI.cs). It detects Mac by calling the + /// uname system command and checking the response, which is always 'Darwin' for MacOS. + /// + private static bool IsRunningMac() + { + IntPtr buffer = IntPtr.Zero; + try + { + buffer = Marshal.AllocHGlobal(8192); + if (EnvironmentUtility.uname(buffer) == 0) + { + string os = Marshal.PtrToStringAnsi(buffer); + return os == "Darwin"; + } + return false; + } + catch + { + return false; // default to Linux + } + finally + { + if (buffer != IntPtr.Zero) + Marshal.FreeHGlobal(buffer); + } + } + } +} diff --git a/src/SMAPI.Toolkit/Utilities/Platform.cs b/src/SMAPI.Toolkit/Utilities/Platform.cs new file mode 100644 index 00000000..d64cbeb9 --- /dev/null +++ b/src/SMAPI.Toolkit/Utilities/Platform.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Toolkit.Utilities +{ + /// The game's platform version. + public enum Platform + { + /// The Linux version of the game. + Linux, + + /// The Mac version of the game. + Mac, + + /// The Windows version of the game. + Windows + } +} -- cgit From a7544e5afb4ba7f12d248503106cc8d407c2bad1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Jun 2019 18:51:12 -0400 Subject: move game detection into toolkit for reuse --- .../Framework/GameScanning/GameScanner.cs | 129 +++++++++++++++++++++ src/SMAPI.Toolkit/ModToolkit.cs | 9 +- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 1 + 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs new file mode 100644 index 00000000..1755a7db --- /dev/null +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using StardewModdingAPI.Toolkit.Utilities; +#if SMAPI_FOR_WINDOWS +using Microsoft.Win32; +#endif + +namespace StardewModdingAPI.Toolkit.Framework.GameScanning +{ + /// Finds installed game folders. + public class GameScanner + { + /********* + ** Public methods + *********/ + /// Find all valid Stardew Valley install folders. + /// This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS. + public IEnumerable Scan() + { + // get unique candidate paths + Platform platform = EnvironmentUtility.DetectPlatform(); + string executableFilename = EnvironmentUtility.GetExecutableName(platform); + IEnumerable paths = this.GetDefaultInstallPaths(platform).Select(PathUtilities.NormalisePathSeparators).Distinct(StringComparer.InvariantCultureIgnoreCase); + + // get valid folders + foreach (string path in paths) + { + DirectoryInfo folder = new DirectoryInfo(path); + if (folder.Exists && folder.EnumerateFiles(executableFilename).Any()) + yield return folder; + } + } + + + /********* + ** Private methods + *********/ + /// The default file paths where Stardew Valley can be installed. + /// The target platform. + /// Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. + private IEnumerable GetDefaultInstallPaths(Platform platform) + { + switch (platform) + { + case Platform.Linux: + case Platform.Mac: + { + string home = Environment.GetEnvironmentVariable("HOME"); + + // Linux + yield return $"{home}/GOG Games/Stardew Valley/game"; + yield return Directory.Exists($"{home}/.steam/steam/steamapps/common/Stardew Valley") + ? $"{home}/.steam/steam/steamapps/common/Stardew Valley" + : $"{home}/.local/share/Steam/steamapps/common/Stardew Valley"; + + // Mac + yield return "/Applications/Stardew Valley.app/Contents/MacOS"; + yield return $"{home}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"; + } + break; + + case Platform.Windows: + { + // Windows + foreach (string programFiles in new[] { @"C:\Program Files", @"C:\Program Files (x86)" }) + { + yield return $@"{programFiles}\GalaxyClient\Games\Stardew Valley"; + yield return $@"{programFiles}\GOG Galaxy\Games\Stardew Valley"; + yield return $@"{programFiles}\Steam\steamapps\common\Stardew Valley"; + } + + // Windows registry +#if SMAPI_FOR_WINDOWS + IDictionary registryKeys = new Dictionary + { + [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam + [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows + }; + foreach (var pair in registryKeys) + { + string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); + if (!string.IsNullOrWhiteSpace(path)) + yield return path; + } + + // via Steam library path + string steampath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); + if (steampath != null) + yield return Path.Combine(steampath.Replace('/', '\\'), @"steamapps\common\Stardew Valley"); +#endif + } + break; + + default: + throw new InvalidOperationException($"Unknown platform '{platform}'."); + } + } + +#if SMAPI_FOR_WINDOWS + /// Get the value of a key in the Windows HKLM registry. + /// The full path of the registry key relative to HKLM. + /// The name of the value. + private string GetLocalMachineRegistryValue(string key, string name) + { + RegistryKey localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine; + RegistryKey openKey = localMachine.OpenSubKey(key); + if (openKey == null) + return null; + using (openKey) + return (string)openKey.GetValue(name); + } + + /// Get the value of a key in the Windows HKCU registry. + /// The full path of the registry key relative to HKCU. + /// The name of the value. + private string GetCurrentUserRegistryValue(string key, string name) + { + RegistryKey currentuser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser; + RegistryKey openKey = currentuser.OpenSubKey(key); + if (openKey == null) + return null; + using (openKey) + return (string)openKey.GetValue(name); + } +#endif + } +} diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index f1da62a2..4b026b7a 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; +using StardewModdingAPI.Toolkit.Framework.GameScanning; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.ModScanning; using StardewModdingAPI.Toolkit.Serialisation; @@ -46,6 +47,13 @@ namespace StardewModdingAPI.Toolkit this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}"; } + /// Find valid Stardew Valley install folders. + /// This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS. + public IEnumerable GetGameFolders() + { + return new GameScanner().Scan(); + } + /// Extract mod metadata from the wiki compatibility list. public async Task GetWikiCompatibilityListAsync() { @@ -72,7 +80,6 @@ namespace StardewModdingAPI.Toolkit /// Extract information about all mods in the given folder. /// The root folder containing mods. Only the will be searched, but this field allows it to be treated as a potential mod folder of its own. /// The mod path to search. - // /// If the folder contains multiple XNB mods, treat them as subfolders of a single mod. This is useful when reading a single mod archive, as opposed to a mods folder. public IEnumerable GetModFolders(string rootPath, string modPath) { return new ModScanner(this.JsonHelper).GetModFolders(rootPath, modPath); diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 2be84316..53bbe1ac 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -19,6 +19,7 @@ + -- cgit From 1a8c7345c3986acd172f91716bbf04fc7192317b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Jun 2019 18:51:31 -0400 Subject: add stardewvalley.targets support to toolkit --- .../Framework/GameScanning/GameScanner.cs | 46 ++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 1755a7db..60c7a682 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; using StardewModdingAPI.Toolkit.Utilities; #if SMAPI_FOR_WINDOWS using Microsoft.Win32; @@ -19,12 +21,18 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning /// This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS. public IEnumerable Scan() { - // get unique candidate paths + // get OS info Platform platform = EnvironmentUtility.DetectPlatform(); string executableFilename = EnvironmentUtility.GetExecutableName(platform); - IEnumerable paths = this.GetDefaultInstallPaths(platform).Select(PathUtilities.NormalisePathSeparators).Distinct(StringComparer.InvariantCultureIgnoreCase); - // get valid folders + // get install paths + IEnumerable paths = this + .GetCustomInstallPaths(platform) + .Concat(this.GetDefaultInstallPaths(platform)) + .Select(PathUtilities.NormalisePathSeparators) + .Distinct(StringComparer.InvariantCultureIgnoreCase); + + // yield valid folders foreach (string path in paths) { DirectoryInfo folder = new DirectoryInfo(path); @@ -98,6 +106,38 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning } } + /// Get the custom install path from the stardewvalley.targets file in the home directory, if any. + /// The target platform. + private IEnumerable GetCustomInstallPaths(Platform platform) + { + // get home path + string homePath = Environment.GetEnvironmentVariable(platform == Platform.Windows ? "USERPROFILE" : "HOME"); + if (string.IsNullOrWhiteSpace(homePath)) + yield break; + + // get targets file + FileInfo file = new FileInfo(Path.Combine(homePath, "stardewvalley.targets")); + if (!file.Exists) + yield break; + + // parse file + XElement root; + try + { + using (FileStream stream = file.OpenRead()) + root = XElement.Load(stream); + } + catch + { + yield break; + } + + // get install path + XElement element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace + if (!string.IsNullOrWhiteSpace(element?.Value)) + yield return element.Value.Trim(); + } + #if SMAPI_FOR_WINDOWS /// Get the value of a key in the Windows HKLM registry. /// The full path of the registry key relative to HKLM. -- cgit From f18ad1210cd813d6ddff665841ac712d62d18b1f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Jul 2019 21:10:00 -0400 Subject: update project name --- src/SMAPI.Toolkit/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs index 1bb19e8c..ec873f79 100644 --- a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs +++ b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("SMAPI.Toolkit")] [assembly: AssemblyDescription("A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.")] [assembly: InternalsVisibleTo("StardewModdingAPI")] -[assembly: InternalsVisibleTo("StardewModdingAPI.Web")] +[assembly: InternalsVisibleTo("SMAPI.Web")] -- cgit From 48f211f5447ec7580b9f9bba63eab0ec6b1ec5c7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 14 Jul 2019 17:49:33 -0400 Subject: add metadata links to mod compatibility list --- src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs | 10 ++++++++++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs | 6 ++++++ 2 files changed, 16 insertions(+) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 3e9b8ea6..24aea5e8 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -127,6 +127,15 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } } + // parse links + List> metadataLinks = new List>(); + foreach (HtmlNode linkElement in node.Descendants("td").Last().Descendants("a").Skip(1)) // skip anchor link + { + string text = linkElement.InnerText.Trim(); + Uri url = new Uri(linkElement.GetAttributeValue("href", "")); + metadataLinks.Add(Tuple.Create(url, text)); + } + // yield model yield return new WikiModEntry { @@ -143,6 +152,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki Compatibility = compatibility, BetaCompatibility = betaCompatibility, Warnings = warnings, + MetadataLinks = metadataLinks.ToArray(), Anchor = anchor }; } diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs index cf416cc6..556796c5 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// A mod entry in the wiki list. @@ -48,6 +51,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// The human-readable warnings for players about this mod. public string[] Warnings { get; set; } + /// Extra metadata links (usually for open pull requests). + public Tuple[] MetadataLinks { get; set; } + /// The link anchor for the mod entry in the wiki compatibility list. public string Anchor { get; set; } } -- cgit From 1bf399ec23368a0666203298768a4b24b63d6a88 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 14 Jul 2019 22:27:00 -0400 Subject: add dev note field to compatibility list --- src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs | 3 +++ 2 files changed, 5 insertions(+) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 24aea5e8..ab6a2517 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -99,6 +99,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki string customUrl = this.GetAttribute(node, "data-url"); string anchor = this.GetAttribute(node, "id"); string contentPackFor = this.GetAttribute(node, "data-content-pack-for"); + string devNote = this.GetAttribute(node, "data-dev-note"); // parse stable compatibility WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo @@ -153,6 +154,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki BetaCompatibility = betaCompatibility, Warnings = warnings, MetadataLinks = metadataLinks.ToArray(), + DevNote = devNote, Anchor = anchor }; } diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs index 556796c5..fff86891 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs @@ -54,6 +54,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// Extra metadata links (usually for open pull requests). public Tuple[] MetadataLinks { get; set; } + /// Special notes intended for developers who maintain unofficial updates or submit pull requests. + public string DevNote { get; set; } + /// The link anchor for the mod entry in the wiki compatibility list. public string Anchor { get; set; } } -- cgit From b2134035b78c06362a4b973502e679be0b5a0de3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 17 Jul 2019 13:07:59 -0400 Subject: update NuGet packages --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 53bbe1ac..ad7ecd20 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -15,9 +15,9 @@ - - - + + + -- cgit From 1b807edd00c525974519bef4c2800f9498ce3eba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 19 Jul 2019 16:07:56 -0400 Subject: update HTTP client to fix form-url-encoded error with large log files --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj