summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/ModHelper.cs
blob: 2b562b4ff5f449c7e52bc82e9dc58ac78c1bf07b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System;
using System.IO;
using Newtonsoft.Json;
using StardewModdingAPI.Advanced;
using StardewModdingAPI.Framework.Reflection;

namespace StardewModdingAPI.Framework
{
    /// <summary>Provides simplified APIs for writing mods.</summary>
    internal class ModHelper : IModHelper
    {
        /*********
        ** Properties
        *********/
        /// <summary>The JSON settings to use when serialising and deserialising files.</summary>
        private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            ObjectCreationHandling = ObjectCreationHandling.Replace // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
        };


        /*********
        ** Accessors
        *********/
        /// <summary>The mod directory path.</summary>
        public string DirectoryPath { get; }

        /// <summary>Simplifies access to private game code.</summary>
        public IReflectionHelper Reflection { get; } = new ReflectionHelper();

        /// <summary>Metadata about loaded mods.</summary>
        public IModRegistry ModRegistry { get; }


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="modDirectory">The mod directory path.</param>
        /// <param name="modRegistry">Metadata about loaded mods.</param>
        /// <exception cref="ArgumentException">An argument is null or invalid.</exception>
        /// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception>
        public ModHelper(string modDirectory, IModRegistry modRegistry)
        {
            // validate
            if (modRegistry == null)
                throw new ArgumentException("The mod registry cannot be null.");
            if (string.IsNullOrWhiteSpace(modDirectory))
                throw new ArgumentException("The mod directory cannot be empty.");
            if (!Directory.Exists(modDirectory))
                throw new InvalidOperationException("The specified mod directory does not exist.");

            // initialise
            this.DirectoryPath = modDirectory;
            this.ModRegistry = modRegistry;
        }

        /****
        ** Mod config file
        ****/
        /// <summary>Read the mod's configuration file (and create it if needed).</summary>
        /// <typeparam name="TConfig">The config class type. This should be a plain class that has public properties for the settings you want. These can be complex types.</typeparam>
        public TConfig ReadConfig<TConfig>()
            where TConfig : class, new()
        {
            var config = this.ReadJsonFile<TConfig>("config.json") ?? new TConfig();
            this.WriteConfig(config); // create file or fill in missing fields
            return config;
        }

        /// <summary>Save to the mod's configuration file.</summary>
        /// <typeparam name="TConfig">The config class type.</typeparam>
        /// <param name="config">The config settings to save.</param>
        public void WriteConfig<TConfig>(TConfig config)
            where TConfig : class, new()
        {
            this.WriteJsonFile("config.json", config);
        }

        /****
        ** Generic JSON files
        ****/
        /// <summary>Read a JSON file.</summary>
        /// <typeparam name="TModel">The model type.</typeparam>
        /// <param name="path">The file path relative to the mod directory.</param>
        /// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns>
        public TModel ReadJsonFile<TModel>(string path)
            where TModel : class
        {
            // read file
            string fullPath = Path.Combine(this.DirectoryPath, path);
            string json;
            try
            {
                json = File.ReadAllText(fullPath);
            }
            catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException)
            {
                return null;
            }

            // deserialise model
            TModel model = JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
            if (model is IConfigFile)
            {
                var wrapper = (IConfigFile)model;
                wrapper.ModHelper = this;
                wrapper.FilePath = path;
            }

            return model;
        }

        /// <summary>Save to a JSON file.</summary>
        /// <typeparam name="TModel">The model type.</typeparam>
        /// <param name="path">The file path relative to the mod directory.</param>
        /// <param name="model">The model to save.</param>
        public void WriteJsonFile<TModel>(string path, TModel model)
            where TModel : class
        {
            path = Path.Combine(this.DirectoryPath, path);

            // create directory if needed
            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            // write file
            string json = JsonConvert.SerializeObject(model, this.JsonSettings);
            File.WriteAllText(path, json);
        }
    }
}