1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #pragma once
11 
12 #ifdef ENABLE_SCRIPTING
13 
14 #    include "../../../Context.h"
15 #    include "../../../config/Config.h"
16 #    include "../../../localisation/LocalisationService.h"
17 #    include "../../Duktape.hpp"
18 #    include "../../ScriptEngine.h"
19 
20 namespace OpenRCT2::Scripting
21 {
22     class ScConfiguration
23     {
24     private:
25         bool _isUserConfig{};
26         DukValue _backingObject;
27 
28     public:
29         // context.configuration
ScConfiguration()30         ScConfiguration()
31             : _isUserConfig(true)
32         {
33         }
34 
35         // context.sharedStorage
ScConfiguration(const DukValue & backingObject)36         ScConfiguration(const DukValue& backingObject)
37             : _backingObject(backingObject)
38         {
39         }
40 
Register(duk_context * ctx)41         static void Register(duk_context* ctx)
42         {
43             dukglue_register_method(ctx, &ScConfiguration::getAll, "getAll");
44             dukglue_register_method(ctx, &ScConfiguration::get, "get");
45             dukglue_register_method(ctx, &ScConfiguration::set, "set");
46             dukglue_register_method(ctx, &ScConfiguration::has, "has");
47         }
48 
49     private:
GetNextNamespace(std::string_view input) const50         std::pair<std::string_view, std::string_view> GetNextNamespace(std::string_view input) const
51         {
52             auto pos = input.find('.');
53             if (pos == std::string_view::npos)
54             {
55                 return std::make_pair(input, std::string_view());
56             }
57 
58             return std::make_pair(input.substr(0, pos), input.substr(pos + 1));
59         }
60 
GetNamespaceAndKey(std::string_view input) const61         std::pair<std::string_view, std::string_view> GetNamespaceAndKey(std::string_view input) const
62         {
63             auto pos = input.find_last_of('.');
64             return pos == std::string_view::npos ? std::make_pair(std::string_view(), input)
65                                                  : std::make_pair(input.substr(0, pos), input.substr(pos + 1));
66         }
67 
GetNamespaceObject(std::string_view ns) const68         std::optional<DukValue> GetNamespaceObject(std::string_view ns) const
69         {
70             auto store = _backingObject;
71             auto k = ns;
72             bool end;
73             do
74             {
75                 auto [next, remainder] = GetNextNamespace(k);
76                 store = store[next];
77                 k = remainder;
78                 end = store.type() == DukValue::Type::UNDEFINED || remainder.empty();
79             } while (!end);
80             return store.type() == DukValue::OBJECT ? std::make_optional(store) : std::nullopt;
81         }
82 
GetOrCreateNamespaceObject(duk_context * ctx,std::string_view ns) const83         DukValue GetOrCreateNamespaceObject(duk_context* ctx, std::string_view ns) const
84         {
85             auto store = _backingObject;
86             if (!ns.empty())
87             {
88                 std::string_view k = ns;
89                 bool end;
90                 do
91                 {
92                     auto [next, remainder] = GetNextNamespace(k);
93                     auto subStore = store[next];
94                     k = remainder;
95                     if (subStore.type() == DukValue::Type::UNDEFINED)
96                     {
97                         store.push();
98                         duk_push_object(ctx);
99                         store = DukValue::copy_from_stack(ctx);
100                         duk_put_prop_lstring(ctx, -2, next.data(), next.size());
101                         duk_pop(ctx);
102                     }
103                     else
104                     {
105                         store = subStore;
106                     }
107                     end = remainder.empty();
108                 } while (!end);
109             }
110             return store;
111         }
112 
IsValidNamespace(std::string_view ns) const113         bool IsValidNamespace(std::string_view ns) const
114         {
115             if (ns.empty() || ns[0] == '.' || ns[ns.size() - 1] == '.')
116             {
117                 return false;
118             }
119             for (size_t i = 1; i < ns.size() - 1; i++)
120             {
121                 if (ns[i - 1] == '.' && ns[i] == '.')
122                 {
123                     return false;
124                 }
125             }
126             return true;
127         }
128 
IsValidKey(std::string_view key) const129         bool IsValidKey(std::string_view key) const
130         {
131             return !key.empty() && key.find('.') == std::string_view::npos;
132         }
133 
getAll(const std::string & ns) const134         DukValue getAll(const std::string& ns) const
135         {
136             DukValue result;
137             auto ctx = GetContext()->GetScriptEngine().GetContext();
138             if (IsValidNamespace(ns))
139             {
140                 if (_isUserConfig)
141                 {
142                     DukObject obj(ctx);
143                     if (ns == "general")
144                     {
145                         obj.Set("general.language", gConfigGeneral.language);
146                         obj.Set("general.showFps", gConfigGeneral.show_fps);
147                     }
148                     result = obj.Take();
149                 }
150                 else
151                 {
152                     auto obj = GetNamespaceObject(ns);
153                     result = obj ? *obj : DukObject(ctx).Take();
154                 }
155             }
156             else
157             {
158                 duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
159             }
160             return result;
161         }
162 
get(const std::string & key,const DukValue & defaultValue) const163         DukValue get(const std::string& key, const DukValue& defaultValue) const
164         {
165             auto ctx = GetContext()->GetScriptEngine().GetContext();
166             if (_isUserConfig)
167             {
168                 if (key == "general.language")
169                 {
170                     auto& localisationService = GetContext()->GetLocalisationService();
171                     auto language = localisationService.GetCurrentLanguage();
172                     auto locale = "";
173                     if (language >= 0 && static_cast<size_t>(language) < std::size(LanguagesDescriptors))
174                     {
175                         locale = LanguagesDescriptors[language].locale;
176                     }
177                     duk_push_string(ctx, locale);
178                     return DukValue::take_from_stack(ctx);
179                 }
180                 if (key == "general.showFps")
181                 {
182                     duk_push_boolean(ctx, gConfigGeneral.show_fps);
183                     return DukValue::take_from_stack(ctx);
184                 }
185             }
186             else
187             {
188                 auto [ns, n] = GetNamespaceAndKey(key);
189                 if (!IsValidNamespace(ns))
190                 {
191                     duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
192                 }
193                 else if (!IsValidKey(n))
194                 {
195                     duk_error(ctx, DUK_ERR_ERROR, "Key was invalid.");
196                 }
197                 else
198                 {
199                     auto obj = GetNamespaceObject(ns);
200                     if (obj)
201                     {
202                         auto val = (*obj)[n];
203                         if (val.type() != DukValue::Type::UNDEFINED)
204                         {
205                             return val;
206                         }
207                     }
208                 }
209             }
210             return defaultValue;
211         }
212 
set(const std::string & key,const DukValue & value) const213         void set(const std::string& key, const DukValue& value) const
214         {
215             auto& scriptEngine = GetContext()->GetScriptEngine();
216             auto ctx = scriptEngine.GetContext();
217             if (_isUserConfig)
218             {
219                 try
220                 {
221                     if (key == "general.showFps")
222                     {
223                         gConfigGeneral.show_fps = value.as_bool();
224                     }
225                     else
226                     {
227                         duk_error(ctx, DUK_ERR_ERROR, "Property does not exist.");
228                     }
229                 }
230                 catch (const DukException&)
231                 {
232                     duk_error(ctx, DUK_ERR_ERROR, "Invalid value for this property.");
233                 }
234             }
235             else
236             {
237                 auto [ns, n] = GetNamespaceAndKey(key);
238                 if (!IsValidNamespace(ns))
239                 {
240                     duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
241                 }
242                 else if (!IsValidKey(n))
243                 {
244                     duk_error(ctx, DUK_ERR_ERROR, "Key was invalid.");
245                 }
246                 else
247                 {
248                     auto obj = GetOrCreateNamespaceObject(ctx, ns);
249                     obj.push();
250                     if (value.type() == DukValue::Type::UNDEFINED)
251                     {
252                         duk_del_prop_lstring(ctx, -1, n.data(), n.size());
253                     }
254                     else
255                     {
256                         value.push();
257                         duk_put_prop_lstring(ctx, -2, n.data(), n.size());
258                     }
259                     duk_pop(ctx);
260 
261                     scriptEngine.SaveSharedStorage();
262                 }
263             }
264         }
265 
has(const std::string & key) const266         bool has(const std::string& key) const
267         {
268             auto val = get(key, DukValue());
269             return val.type() != DukValue::Type::UNDEFINED;
270         }
271     };
272 } // namespace OpenRCT2::Scripting
273 
274 #endif
275