diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index d30967fe5..bc97dedf8 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -22,6 +22,7 @@ This assumes `find-game-folder.targets` has already been imported and validated. + diff --git a/build/unix/prepare-install-package.sh b/build/unix/prepare-install-package.sh index cfd24afd4..b70fbca56 100755 --- a/build/unix/prepare-install-package.sh +++ b/build/unix/prepare-install-package.sh @@ -134,7 +134,7 @@ for folder in ${folders[@]}; do cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do + for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Newtonsoft.Json.Schema.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do cp "$smapiBin/$name" "$bundlePath/smapi-internal" done diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 48c013ff0..00a719efe 100644 --- a/build/windows/prepare-install-package.ps1 +++ b/build/windows/prepare-install-package.ps1 @@ -155,7 +155,7 @@ foreach ($folder in $folders) { cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { + foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Newtonsoft.Json.Schema.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { cp "$smapiBin/$name" "$bundlePath/smapi-internal" } diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 0552a23e1..a1ae915af 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -14,19 +14,26 @@ namespace StardewModdingAPI.Toolkit.Serialization public class JsonHelper { /********* - ** Accessors + ** Fields *********/ - /// The JSON settings to use when serializing and deserializing files. - public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); - - private readonly JSchemaGenerator _schemaGenerator = new(); + /// The JSON schema generator to use when creating a schema file. + private readonly JSchemaGenerator SchemaGenerator = new(); - private readonly JSchemaWriterSettings _schemaWriterSettings = new() + /// The JSON settings to use when creating a schema file. + private readonly JSchemaWriterSettings SchemaWriterSettings = new() { Version = SchemaVersion.Draft2019_09, ReferenceHandling = JSchemaWriterReferenceHandling.Never }; + + /********* + ** Accessors + *********/ + /// The JSON settings to use when serializing and deserializing files. + public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); + + /********* ** Public methods *********/ @@ -109,9 +116,7 @@ public void WriteJsonFile(string fullPath, TModel model) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed - string dir = Path.GetDirectoryName(fullPath)!; - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + string dir = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); @@ -120,7 +125,7 @@ public void WriteJsonFile(string fullPath, TModel model) File.WriteAllText(fullPath, json); } - /// Generate a schema and save to a JSON file. + /// Save a data model schema to a JSON file. /// The model type. /// The absolute file path. /// The given path is empty or invalid. @@ -132,16 +137,14 @@ public void WriteJsonSchemaFile(string fullPath) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed - string dir = Path.GetDirectoryName(fullPath)!; - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + string dir = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - JSchema schema = this._schemaGenerator.Generate(typeof(TModel)); - string output = schema.ToString(this._schemaWriterSettings); - - File.WriteAllText(fullPath, output); + // write file + JSchema schema = this.SchemaGenerator.Generate(typeof(TModel)); + string json = schema.ToString(this.SchemaWriterSettings); + File.WriteAllText(fullPath, json); } /// Deserialize JSON text if possible. diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index dd8cd373e..69b20b1ac 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -28,7 +28,7 @@ internal class DataHelper : BaseHelper, IDataHelper /// Construct an instance. /// The mod using this instance. /// The absolute path to the mod folder. - /// The absolute path to the mod folder. + /// Encapsulates SMAPI's JSON file parsing. public DataHelper(IModMetadata mod, string modFolderPath, JsonHelper jsonHelper) : base(mod) { @@ -67,19 +67,21 @@ public void WriteJsonFile(string path, TModel? data) File.Delete(path); } - /// - public void WriteJsonSchemaFile(string path, TModel data) where TModel : class + /// + public void WriteJsonSchemaFile(string path) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) - throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); + throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonSchemaFile)} with a relative path (without directory climbing)."); path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path)); + this.JsonHelper.WriteJsonSchemaFile(path); } /**** - ** Save file - ****/ + ** Save file + ****/ /// public TModel? ReadSaveData(string key) where TModel : class diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 13efbd683..e48052aab 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -11,8 +11,9 @@ internal class ModHelper : BaseHelper, IModHelper, IDisposable /********* ** Fields *********/ - /// Whether to generate config.schema.json files. - private readonly bool _generateConfigSchemas; + /// Whether to generate a config.schema.json file based on the mod's config model when it's loaded or saved. + private readonly bool GenerateConfigSchema; + /********* ** Accessors @@ -71,10 +72,10 @@ internal class ModHelper : BaseHelper, IModHelper, IDisposable /// An API for accessing private game code. /// Provides multiplayer utilities. /// An API for reading translations stored in the mod's i18n folder. - /// Whether to generate config.schema.json files. + /// Whether to generate a config.schema.json file based on the mod's config model when it's loaded or saved. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, bool generateConfigSchemas) + public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, bool generateConfigSchema) : base(mod) { // validate directory @@ -96,7 +97,7 @@ public ModHelper(IModMetadata mod, string modDirectory, Func curren this.Multiplayer = multiplayer ?? throw new ArgumentNullException(nameof(multiplayer)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); this.Events = events; - this._generateConfigSchemas = generateConfigSchemas; + this.GenerateConfigSchema = generateConfigSchema; } /**** @@ -108,12 +109,6 @@ public TConfig ReadConfig() { TConfig config = this.Data.ReadJsonFile("config.json") ?? new TConfig(); this.WriteConfig(config); // create file or fill in missing fields - - if (this._generateConfigSchemas) - { - this.WriteConfigSchema(config); - } - return config; } @@ -122,12 +117,9 @@ public void WriteConfig(TConfig config) where TConfig : class, new() { this.Data.WriteJsonFile("config.json", config); - } - private void WriteConfigSchema(TConfig config) - where TConfig : class, new() - { - this.Data.WriteJsonSchemaFile("config.schema.json", config); + if (this.GenerateConfigSchema) + this.Data.WriteJsonSchemaFile("config.schema.json"); } /**** diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 42c9e0b52..4c439dab3 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -25,7 +25,7 @@ internal class SConfig [nameof(RewriteMods)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux, [nameof(SuppressHarmonyDebugMode)] = true, - [nameof(GenerateConfigSchemas)] = false, + [nameof(GenerateConfigSchemas)] = false }; /// The default values for , to log changes if different. @@ -92,9 +92,10 @@ internal class SConfig /// The mod IDs SMAPI should load after any other mods. public HashSet ModsToLoadLate { get; set; } - /// Whether to generate config.schema.json files for external tools like mod managers. + /// Whether to generate config.schema.json files for external tools like mod managers. public bool GenerateConfigSchemas { get; set; } + /******** ** Public methods ********/ diff --git a/src/SMAPI/IDataHelper.cs b/src/SMAPI/IDataHelper.cs index 23d026e31..ff778bc24 100644 --- a/src/SMAPI/IDataHelper.cs +++ b/src/SMAPI/IDataHelper.cs @@ -27,12 +27,11 @@ public interface IDataHelper void WriteJsonFile(string path, TModel? data) where TModel : class; - /// Save the schema of the data to a JSON file in the mod's folder. + /// Save a data model schema to a JSON file in the mod's folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The file path relative to the mod folder. - /// The arbitrary data to save. /// The is not relative or contains directory climbing (../). - void WriteJsonSchemaFile(string path, TModel data) + void WriteJsonSchemaFile(string path) where TModel : class; /**** diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 1bd467d97..4e64707cb 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -158,5 +158,12 @@ in future SMAPI versions. * the mod author. */ "ModsToLoadEarly": [], - "ModsToLoadLate": [] + "ModsToLoadLate": [], + + /** + * Whether to generate a `config.schema.json` file next to each mod's `config.json` file. + * + * This can be used by separate tools like mod managers to enable config editing features. + */ + "GenerateConfigSchemas": false }