1 /*
2     C-Dogs SDL
3     A port of the legendary (and fun) action/arcade cdogs.
4 
5     Copyright (c) 2013-2014, 2016, Cong Xu
6     All rights reserved.
7 
8     Redistribution and use in source and binary forms, with or without
9     modification, are permitted provided that the following conditions are met:
10 
11     Redistributions of source code must retain the above copyright notice, this
12     list of conditions and the following disclaimer.
13     Redistributions in binary form must reproduce the above copyright notice,
14     this list of conditions and the following disclaimer in the documentation
15     and/or other materials provided with the distribution.
16 
17     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27     POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "config_json.h"
30 
31 #include <stdio.h>
32 
33 #include <json/json.h>
34 
35 #include "config.h"
36 #include "json_utils.h"
37 #include "keyboard.h"
38 #include "log.h"
39 
40 #ifdef __EMSCRIPTEN__
41 #include <emscripten.h>
42 #endif
43 
44 static void ConfigLoadVisit(Config *c, json_t *node);
ConfigLoadJSON(Config * config,const char * filename)45 void ConfigLoadJSON(Config *config, const char *filename)
46 {
47 	FILE *f = fopen(filename, "r");
48 	json_t *root = NULL;
49 	int version;
50 
51 	if (f == NULL)
52 	{
53 		printf("Error loading config '%s'\n", filename);
54 		goto bail;
55 	}
56 
57 	if (json_stream_parse(f, &root) != JSON_OK)
58 	{
59 		printf("Error parsing config '%s'\n", filename);
60 		goto bail;
61 	}
62 	LoadInt(&version, root, "Version");
63 	ConfigLoadVisit(config, root);
64 
65 	// Old config version stuff
66 	if (version < 8)
67 	{
68 		json_t *node = JSONFindNode(root, "Game");
69 		if (node != NULL)
70 		{
71 			Config *c = ConfigGet(config, "Graphics.Shadows");
72 			LoadBool(&c->u.Bool.Value, node, c->Name);
73 			c = ConfigGet(config, "Graphics.Gore");
74 			JSON_UTILS_LOAD_ENUM(
75 				c->u.Enum.Value, node, c->Name, c->u.Enum.StrToEnum);
76 		}
77 	}
78 	if (version < 5)
79 	{
80 		json_t *node = JSONFindNode(root, "Game");
81 		if (node != NULL)
82 		{
83 			bool moveWhenShooting = false;
84 			LoadBool(&moveWhenShooting, node, "MoveWhenShooting");
85 			Config *c = ConfigGet(config, "Game.FireMoveStyle");
86 			c->u.Enum.Value = moveWhenShooting ? FIREMOVE_NORMAL : FIREMOVE_STOP;
87 		}
88 	}
89 	if (version < 4)
90 	{
91 		json_t *node = JSONFindNode(root, "Interface");
92 		if (node != NULL)
93 		{
94 			bool splitscreenAlways;
95 			LoadBool(&splitscreenAlways, node, "SplitscreenAlways");
96 			Config *c = ConfigGet(config, "Interface.Splitscreen");
97 			c->u.Enum.Value =
98 				splitscreenAlways ? SPLITSCREEN_ALWAYS : SPLITSCREEN_NORMAL;
99 		}
100 	}
101 
102 bail:
103 	json_free_value(&root);
104 	if (f != NULL)
105 	{
106 		fclose(f);
107 	}
108 }
ConfigLoadVisit(Config * c,json_t * node)109 static void ConfigLoadVisit(Config *c, json_t *node)
110 {
111 	if (node == NULL)
112 	{
113 		LOG(LM_MAIN, LL_WARN, "Error loading config: node %s not found",
114 			c->Name);
115 		return;
116 	}
117 	switch (c->Type)
118 	{
119 	case CONFIG_TYPE_STRING:
120 		CASSERT(false, "not implemented");
121 		break;
122 	case CONFIG_TYPE_INT:
123 		LoadInt(&c->u.Int.Value, node, c->Name);
124 		if (c->u.Int.Min > 0)
125 			c->u.Int.Value = MAX(c->u.Int.Value, c->u.Int.Min);
126 		if (c->u.Int.Max > 0)
127 			c->u.Int.Value = MIN(c->u.Int.Value, c->u.Int.Max);
128 		break;
129 	case CONFIG_TYPE_FLOAT:
130 		LoadDouble(&c->u.Float.Value, node, c->Name);
131 		if (c->u.Float.Min > 0)
132 			c->u.Float.Value = MAX(c->u.Float.Value, c->u.Float.Min);
133 		if (c->u.Float.Max > 0)
134 			c->u.Float.Value = MIN(c->u.Float.Value, c->u.Float.Max);
135 		break;
136 	case CONFIG_TYPE_BOOL:
137 		LoadBool(&c->u.Bool.Value, node, c->Name);
138 		break;
139 	case CONFIG_TYPE_ENUM:
140 		JSON_UTILS_LOAD_ENUM(
141 			c->u.Enum.Value, node, c->Name, c->u.Enum.StrToEnum);
142 		if (c->u.Enum.Min > 0)
143 			c->u.Enum.Value = MAX(c->u.Enum.Value, c->u.Enum.Min);
144 		if (c->u.Enum.Max > 0)
145 			c->u.Enum.Value = MIN(c->u.Enum.Value, c->u.Enum.Max);
146 		break;
147 	case CONFIG_TYPE_GROUP:
148 		{
149 			// If the config has no name, then it is the root element
150 			// Load children directly to the node
151 			// Otherwise, find the named child
152 			if (c->Name != NULL)
153 			{
154 				node = json_find_first_label(node, c->Name);
155 				if (node == NULL)
156 				{
157 					LOG(LM_MAIN, LL_WARN,
158 						"Error loading config: node %s not found", c->Name);
159 					return;
160 				}
161 				node = node->child;
162 			}
163 			CA_FOREACH(Config, child, c->u.Group)
164 				ConfigLoadVisit(child, node);
165 			CA_FOREACH_END()
166 		}
167 		break;
168 	default:
169 		CASSERT(false, "Unknown config type");
170 		break;
171 	}
172 }
173 
174 static void ConfigSaveVisit(const Config *c, json_t *node);
ConfigSaveJSON(const Config * config,const char * filename)175 void ConfigSaveJSON(const Config *config, const char *filename)
176 {
177 	FILE *f = fopen(filename, "w");
178 	char *text = NULL;
179 	json_t *root;
180 
181 	if (f == NULL)
182 	{
183 		printf("Error saving config '%s'\n", filename);
184 		return;
185 	}
186 
187 	root = json_new_object();
188 	json_insert_pair_into_object(
189 		root, "Version", json_new_number(TOSTRING(CONFIG_VERSION)));
190 	ConfigSaveVisit(config, root);
191 
192 	json_tree_to_string(root, &text);
193 	char *formatText = json_format_string(text);
194 	fputs(formatText, f);
195 
196 	// clean up
197 	CFREE(formatText);
198 	CFREE(text);
199 	json_free_value(&root);
200 
201 	fclose(f);
202 
203 #ifdef __EMSCRIPTEN__
204     EM_ASM(
205         //persist changes
206         FS.syncfs(false,function (err) {
207                           assert(!err);
208         });
209     );
210 #endif
211 }
ConfigSaveVisit(const Config * c,json_t * node)212 static void ConfigSaveVisit(const Config *c, json_t *node)
213 {
214 	switch (c->Type)
215 	{
216 	case CONFIG_TYPE_STRING:
217 		CASSERT(false, "not implemented");
218 		break;
219 	case CONFIG_TYPE_INT:
220 		AddIntPair(node, c->Name, c->u.Int.Value);
221 		break;
222 	case CONFIG_TYPE_FLOAT:
223 		CASSERT(false, "not implemented");
224 		break;
225 	case CONFIG_TYPE_BOOL:
226 		json_insert_pair_into_object(
227 			node, c->Name, json_new_bool(c->u.Bool.Value));
228 		break;
229 	case CONFIG_TYPE_ENUM:
230 		JSON_UTILS_ADD_ENUM_PAIR(
231 			node, c->Name, c->u.Enum.Value, c->u.Enum.EnumToStr);
232 		break;
233 	case CONFIG_TYPE_GROUP:
234 		{
235 			json_t *child = node;
236 			// If the config has no name, then it is the root element
237 			// Add children directly to the node
238 			// Otherwise, create a new child
239 			if (c->Name != NULL)
240 			{
241 				child = json_new_object();
242 			}
243 			CA_FOREACH(Config, cg, c->u.Group)
244 				ConfigSaveVisit(cg, child);
245 			CA_FOREACH_END()
246 			if (c->Name != NULL)
247 			{
248 				json_insert_pair_into_object(node, c->Name, child);
249 			}
250 		}
251 		break;
252 	default:
253 		CASSERT(false, "Unknown config type");
254 		break;
255 	}
256 }
257 
ConfigGetJSONVersion(FILE * f)258 int ConfigGetJSONVersion(FILE *f)
259 {
260 	json_t *root = NULL;
261 	json_t *child = NULL;
262 	int version = -1;
263 
264 	if (json_stream_parse(f, &root) != JSON_OK)
265 	{
266 		printf("Error parsing JSON config\n");
267 		goto bail;
268 	}
269 	child = json_find_first_label(root, "Version");
270 	if (child == NULL)
271 	{
272 		printf("Error parsing JSON config version\n");
273 		goto bail;
274 	}
275 	version = atoi(child->child->text);
276 
277 bail:
278 	json_free_value(&root);
279 	return version;
280 }
281