1 /*
2 	Provide functions to load and save configuration files using json format.
3 
4 	Replacing previous binary formats with portable text files.
5 	See https://github.com/raduprv/Eternal-Lands/issues/71
6 
7 	Author bluap/pjbroad April 2020
8 	Stay at home. Save lives. Protect the NHS.
9 */
10 #include <iostream>
11 #include <fstream>
12 #include <string>
13 #include <iomanip>
14 #include <nlohmann/json.hpp>
15 
16 #include "chat.h"
17 #include "elloggingwrapper.h"
18 #include "counters.h"
19 #include "manufacture.h"
20 #include "text.h"
21 
22 //	Helper functions
23 //
24 namespace JSON_IO
25 {
get_json_indent(void)26 	static size_t get_json_indent(void)
27 		{ return 0; } // 0 is compact, non-zero give pretty output, 4 for example
exit_error(const char * function,size_t line,const std::string & message,int error_code)28 	static int exit_error(const char *function, size_t line, const std::string& message, int error_code)
29 		{ LOG_ERROR("%s:%ld %s", function, line, message.c_str()); return error_code; }
info_message(const char * function,size_t line,std::string message)30 	static void info_message(const char *function, size_t line, std::string message)
31 		{ LOG_INFO("%s:%ld %s", function, line, message.c_str()); }
console_message(const std::string & file_type,const std::string & message)32 	static void console_message(const std::string& file_type, const std::string& message)
33 		{ std::string full_message = "Problem with " + file_type + ": " + message; LOG_TO_CONSOLE(c_red3, full_message.c_str()); }
file_format_error(const std::string & file_type)34 	static void file_format_error(const std::string& file_type)
35 		{ console_message(file_type, "File format error. " + file_type + " will not be saved until this is corrected."); }
36 }
37 
38 
39 namespace JSON_IO_Recipes
40 {
41 	using json = nlohmann::json;
42 
43 
44 	//	A Class to load and save manufacture recipes in json format.
45 	//
46 	class Recipes
47 	{
48 		public:
Recipes(void)49 			Recipes(void) : opened(false), parse_error(false) {}
50 			int open(const char *file_name);
51 			int load(recipe_entry *recipes_store, size_t max_recipes);
52 			int save(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe);
53 		private:
54 			bool opened;			// we have opened the file and populated the read_json object
55 			bool parse_error;		// there was an error populating the json object
56 			json read_json;			// the complete json object read from file
57 			const char * class_name_str = "Recipes";
58 	};
59 
60 
61 	//	Read the json from file and return the number of recipes, or -1 for error.
62 	//
open(const char * file_name)63 	int Recipes::open(const char *file_name)
64 	{
65 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
66 
67 		std::ifstream in_file(file_name);
68 		if (!in_file)
69 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1);
70 
71 		try
72 		{
73 			in_file >> read_json;
74 		}
75 		catch (json::exception& e)
76 		{
77 			parse_error = true;
78 			JSON_IO::file_format_error(class_name_str);
79 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
80 		}
81 
82 		if (!read_json["recipes"].is_array())
83 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing recipes[]", -1);
84 
85 		opened = true;
86 		return read_json["recipes"].size();
87 	}
88 
89 
90 	//	Parse the json read by open() and store into the recipe array.
91 	//	The recipe array must have been initialised to all zero - calloc().
92 	//	String memory is allocated here but must be freed by the caller.
93 	//	Missing fields should soft fail.
94 	//	Returns the active recipe, or -1 for an error.
95 	//
load(recipe_entry * recipes_store,size_t max_recipes)96 	int Recipes::load(recipe_entry *recipes_store, size_t max_recipes)
97 	{
98 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, "");
99 
100 		if (!recipes_store)
101 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Recipe store is NULL", -1);
102 
103 		if (!opened)
104 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "JSON object not open()ed", -1);
105 
106 		size_t number_of_recipes = (read_json["recipes"].is_array()) ?read_json["recipes"].size() :0;
107 		int cur_recipe = 0;
108 
109 		for (size_t i = 0; i < number_of_recipes && i < max_recipes; i++)
110 		{
111 			json recipe = read_json["recipes"][i];
112 			if (recipe.is_null())
113 				continue;
114 			json items = recipe["items"];
115 			if (!items.is_array())
116 				continue;
117 			for (size_t j = 0; j < NUM_MIX_SLOTS && j < items.size(); j++)
118 			{
119 				json the_item = items[j];
120 				if (the_item.is_null())
121 					continue;
122 				recipes_store[i].items[j].id = (the_item["id"].is_number_unsigned()) ?the_item["id"].get<Uint16>() :unset_item_uid;
123 				recipes_store[i].items[j].image_id = (the_item["image_id"].is_number_integer()) ?the_item["image_id"].get<int>() :0;
124 				recipes_store[i].items[j].quantity = (the_item["quantity"].is_number_integer()) ?the_item["quantity"].get<int>() :0;
125 			}
126 			if (!recipe["name"].is_string())
127 				continue;
128 			std::string name = recipe["name"].get<std::string>();
129 			if (name.size() > 0)
130 			{
131 				recipes_store[i].name = static_cast<char *>(malloc(name.size() + 1));
132 				strcpy(recipes_store[i].name, name.c_str());
133 			}
134 			if (recipe["current"].is_boolean())
135 				cur_recipe = i;
136 		}
137 
138 		// we are done with the json object so free the memory
139 		read_json.clear();
140 		opened = false;
141 
142 		return cur_recipe;
143 	}
144 
145 
146 	//	Save the recipe data in json format.
147 	//	Empty slots in a recipe are not saved.
148 	//	Ids that are unset, are not saved.
149 	//	Null names are saved as empty strings.
150 	//	Return 0 if successful otherwise -1.
151 	//
save(const char * file_name,recipe_entry * recipes_store,size_t num_recipes,int current_recipe)152 	int Recipes::save(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe)
153 	{
154 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
155 
156 		if (parse_error)
157 		{
158 			JSON_IO::file_format_error(class_name_str);
159 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
160 		}
161 
162 		json write_json;
163 
164 		json recipe_list = json::array();
165 		for (size_t i=0; i<num_recipes; i++)
166 		{
167 			json recipe;
168 			json item_list = json::array();
169 			for (size_t j=0; j<NUM_MIX_SLOTS; j++)
170 			{
171 				if (recipes_store[i].items[j].quantity <= 0)
172 					continue;
173 				json one_item;
174 				if (recipes_store[i].items[j].id != unset_item_uid)
175 					one_item["id"] = recipes_store[i].items[j].id;
176 				one_item["image_id"] = recipes_store[i].items[j].image_id;
177 				one_item["quantity"] = recipes_store[i].items[j].quantity;
178 				item_list.push_back(one_item);
179 			}
180 			recipe["items"] = item_list;
181 			recipe["name"] = (recipes_store[i].name) ?recipes_store[i].name :"";
182 			if (current_recipe == static_cast<int>(i))
183 				recipe["current"] = true;
184 			recipe_list.push_back(recipe);
185 		}
186 		write_json["recipes"] = recipe_list;
187 
188 		std::ofstream out_file(file_name);
189 		if (out_file)
190 		{
191 			out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl;
192 			return 0;
193 		}
194 		else
195 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1);
196 	}
197 
198 } // end JSON_IO_Recipes namespace
199 
200 
201 namespace JSON_IO_Quickspells
202 {
203 	using json = nlohmann::json;
204 
205 
206 	//	A Class to load and save quickspells in json format.
207 	//
208 	class Quickspells
209 	{
210 		public:
Quickspells(void)211 			Quickspells(void) : parse_error(false) {}
212 			int load(const char *file_name, int *spell_ids, size_t max_num_spell_id);
213 			int save(const char *file_name, Uint16 *spell_ids, size_t num_spell_id);
214 		private:
215 			bool parse_error;		// there was an error populating the json object
216 			const char * class_name_str = "Quickspells";
217 	};
218 
219 
220 	//	Load the quickspells ids up to the maximum.
221 	//	Return the number read or -1 for an error.
222 	//
load(const char * file_name,int * spell_ids,size_t max_num_spell_id)223 	int Quickspells::load(const char *file_name, int *spell_ids, size_t max_num_spell_id)
224 	{
225 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
226 
227 		std::ifstream in_file(file_name);
228 		if (!in_file)
229 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1);
230 
231 		json read_json;
232 
233 		try
234 		{
235 			in_file >> read_json;
236 		}
237 		catch (json::exception& e)
238 		{
239 			parse_error = true;
240 			JSON_IO::file_format_error(class_name_str);
241 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
242 		}
243 
244 		if (!read_json["quickspells"].is_array())
245 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing quickspells[]", -1);
246 
247 		for (size_t i = 0; i < read_json["quickspells"].size() && i < max_num_spell_id; i++)
248 			spell_ids[i] = (read_json["quickspells"][i].is_number_integer()) ?read_json["quickspells"][i].get<int>() :-1;
249 
250 		return (read_json["quickspells"].size() < max_num_spell_id) ?read_json["quickspells"].size() :max_num_spell_id;
251 	}
252 
253 
254 	//	Save the quickspells ids.
255 	//	Return 0, or -1 on an error
256 	//
save(const char * file_name,Uint16 * spell_ids,size_t num_spell_id)257 	int Quickspells::save(const char *file_name, Uint16 *spell_ids, size_t num_spell_id)
258 	{
259 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
260 
261 		if (parse_error)
262 		{
263 			JSON_IO::file_format_error(class_name_str);
264 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
265 		}
266 
267 		json write_json;
268 
269 		json quickspells_list = json::array();
270 		for (size_t i = 0; i < num_spell_id; i++)
271 			quickspells_list.push_back(spell_ids[i]);
272 		write_json["quickspells"] = quickspells_list;
273 
274 		std::ofstream out_file(file_name);
275 		if (out_file)
276 		{
277 			out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl;
278 			return 0;
279 		}
280 		else
281 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1);
282 	}
283 
284 } // end JSON_IO_Quickspells namespace
285 
286 
287 namespace JSON_IO_Counters
288 {
289 	using json = nlohmann::json;
290 
291 
292 	//	A Class to load and save the counters in json format.
293 	//
294 	class Counters
295 	{
296 		public:
Counters(void)297 			Counters(void) : parse_error(false) {}
298 			int load(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters);
299 			int save(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters);
300 		private:
301 			bool parse_error;		// there was an error populating the json object
302 			const char * class_name_str = "Counters";
303 	};
304 
305 
306 	//	Load the counters.
307 	//	Memory allocated here, must be freed by the caller.
308 	//	Return the number of categories actually read, or -1 on error.
309 	//
load(const char * file_name,const char ** cat_str,int * entries,size_t num_categories,struct Counter ** the_counters)310 	int Counters::load(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters)
311 	{
312 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
313 
314 		std::ifstream in_file(file_name);
315 		if (!in_file)
316 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1);
317 
318 		json read_json;
319 
320 		try
321 		{
322 			in_file >> read_json;
323 		}
324 		catch (json::exception& e)
325 		{
326 			parse_error = true;
327 			JSON_IO::file_format_error(class_name_str);
328 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
329 		}
330 
331 		if (!read_json["categories"].is_array())
332 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing categories[]", -1);
333 
334 		for (size_t i = 0; i < read_json["categories"].size() && i < num_categories; i++)
335 		{
336 			if (!read_json["categories"][i]["name"].is_string() || (read_json["categories"][i]["name"] != std::string(cat_str[i])))
337 				continue;
338 			json entries_list = read_json["categories"][i]["entries"];
339 			if (!entries_list.is_array())
340 				continue;
341 			for (size_t j = 0; j < entries_list.size(); j++)
342 			{
343 				json entry = entries_list[j];
344 				if (!entry["name"].is_string())
345 					continue;
346 				std::string name = entry["name"].get<std::string>();
347 				if (name.empty())
348 					continue;
349 				entries[i]++;
350 				the_counters[i] = (struct Counter *)realloc(the_counters[i], entries[i] * sizeof(struct Counter));
351 				the_counters[i][j].name = static_cast<char *>(malloc(name.size() + 1));
352 				strcpy(the_counters[i][j].name, name.c_str());
353 				the_counters[i][j].n_session = 0;
354 				the_counters[i][j].n_total = (entry["n_total"].is_number_unsigned()) ?entry["n_total"].get<Uint32>() :0;
355 				the_counters[i][j].extra = (entry["extra"].is_number_unsigned()) ?entry["extra"].get<Uint32>() :0;
356 			}
357 		}
358 
359 		return (read_json["categories"].size() < num_categories) ?read_json["categories"].size() :num_categories;
360 	}
361 
362 
363 	//	Save the counters.
364 	//	Return 0, or -1 on error.
365 	//
save(const char * file_name,const char ** cat_str,const int * entries,size_t num_categories,const struct Counter ** the_counters)366 	int Counters::save(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters)
367 	{
368 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
369 
370 		if (parse_error)
371 		{
372 			JSON_IO::file_format_error(class_name_str);
373 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
374 		}
375 
376 		json categories_list = json::array();
377 		for (size_t i = 0; i < num_categories; i++)
378 		{
379 			json category;
380 			category["name"] = cat_str[i];
381 			json entries_list = json::array();
382 			for (int j = 0; j < entries[i]; j++)
383 			{
384 				json entry;
385 				entry["name"] = the_counters[i][j].name;
386 				entry["n_total"] = the_counters[i][j].n_total;
387 				if (the_counters[i][j].extra > 0)
388 					entry["extra"] = the_counters[i][j].extra;
389 				entries_list.push_back(entry);
390 			}
391 			category["entries"] = entries_list;
392 			categories_list.push_back(category);
393 		}
394 
395 		json write_json;
396 		write_json["categories"] = categories_list;
397 
398 		std::ofstream out_file(file_name);
399 		if (out_file)
400 		{
401 			out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl;
402 			return 0;
403 		}
404 		else
405 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1);
406 	}
407 
408 } //end JSON_IO_Counters namespace
409 
410 
411 namespace JSON_IO_Channel_Colours
412 {
413 	using json = nlohmann::json;
414 
415 
416 	//	A Class to load and save the the channel colours in json format.
417 	//
418 	class Channel_Colours
419 	{
420 		public:
Channel_Colours(void)421 			Channel_Colours(void) : parse_error(false) {}
422 			int load(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours);
423 			int save(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours);
424 		private:
425 			bool parse_error;		// there was an error populating the json object
426 			const char * class_name_str = "Channel Colours";
427 	};
428 
429 
430 	//	Load the channel colours.
431 	//	Assumed that he channel colour arrar has been initialised.
432 	//	Return the number of sets actually read, or -1 on error.
433 	//
load(const char * file_name,channelcolor * the_channel_colours,size_t max_channel_colours)434 	int Channel_Colours::load(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours)
435 	{
436 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
437 
438 		std::ifstream in_file(file_name);
439 		if (!in_file)
440 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1);
441 
442 		json read_json;
443 
444 		try
445 		{
446 			in_file >> read_json;
447 		}
448 		catch (json::exception& e)
449 		{
450 			parse_error = true;
451 			JSON_IO::file_format_error(class_name_str);
452 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
453 		}
454 
455 		if (!read_json["channel_colours"].is_array())
456 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing channel_colours[]", -1);
457 
458 		for (size_t i = 0; i < read_json["channel_colours"].size() && i < max_channel_colours; i++)
459 		{
460 			json channel_colour_set = read_json["channel_colours"][i];
461 			if (!channel_colour_set["channel"].is_number_unsigned() || !channel_colour_set["colour"].is_number_integer() ||
462 					(channel_colour_set["colour"] < 0))
463 				continue;
464 			the_channel_colours[i].nr = channel_colour_set["channel"];
465 			the_channel_colours[i].color = channel_colour_set["colour"];
466 		}
467 
468 		return (read_json["channel_colours"].size() < max_channel_colours) ?read_json["channel_colours"].size() :max_channel_colours;
469 	}
470 
471 
472 	//	Save the channel colours.
473 	//	Return 0, or -1 on error.
474 	//
save(const char * file_name,const channelcolor * the_channel_colours,size_t max_channel_colours)475 	int Channel_Colours::save(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours)
476 	{
477 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
478 
479 		if (parse_error)
480 		{
481 			JSON_IO::file_format_error(class_name_str);
482 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
483 		}
484 
485 		json write_json;
486 
487 		json channel_colours_list = json::array();
488 		for (size_t i = 0; i < max_channel_colours; i++)
489 		{
490 			if (the_channel_colours[i].color < 0)
491 				continue;
492 			json channel_colour_set;
493 			channel_colour_set["channel"] = the_channel_colours[i].nr;
494 			channel_colour_set["colour"] = the_channel_colours[i].color;
495 			channel_colours_list.push_back(channel_colour_set);
496 		}
497 		write_json["channel_colours"] = channel_colours_list;
498 
499 		std::ofstream out_file(file_name);
500 		if (out_file)
501 		{
502 			out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl;
503 			return 0;
504 		}
505 		else
506 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1);
507 	}
508 }
509 
510 
511 namespace JSON_IO_Character_Options
512 {
513 	using json = nlohmann::json;
514 
515 
516 	//	A Class to load and save the character options in json format.
517 	//	Character options if present override values in the ini file.
518 	//
519 	class Character_Options
520 	{
521 		public:
Character_Options(void)522 			Character_Options(void) : parse_error(false), loaded(false), modified(false) {}
set_file_name(const char * file_name)523 			void set_file_name(const char *file_name) { the_file_name = std::string(file_name); }
524 			int load(void);
525 			int save(void);
526 			template <class TheType> TheType get(const char *var_name, TheType default_var_value) const;
527 			template <class TheType> void set(const char *var_name, TheType value);
528 			bool exists(const char *var_name) const;
529 			void remove(const char *var_name);
530 		private:
have_options_list(void) const531 			bool have_options_list(void) const { return ((json_data.find(ini_options_list_str) != json_data.end()) && json_data[ini_options_list_str].is_array()); }
532 			bool parse_error;		// there was an error populating the json object
533 			bool loaded;			// true if the file is already loaded
534 			bool modified;			// if true, the object has been modified and needs to be saved
535 			json json_data;			// the json object as read from file, or empty if no file
536 			std::string the_file_name;
537 			const char * class_name_str = "Character Options";
538 			const char * ini_options_list_str = "ini_options";
539 			const char * name_str = "name";
540 			const char * value_str = "value";
541 	};
542 
543 
544 	//	Load the character options.
545 	//	Return 0 or -1 on error.
546 	//
load(void)547 	int Character_Options::load(void)
548 	{
549 		if (loaded)
550 			return 0;
551 
552 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + the_file_name + "]");
553 
554 		std::ifstream in_file(the_file_name);
555 		if (!in_file)
556 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + the_file_name + "]", -1);
557 
558 		try
559 		{
560 			in_file >> json_data;
561 		}
562 		catch (json::exception& e)
563 		{
564 			parse_error = true;
565 			JSON_IO::file_format_error(class_name_str);
566 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
567 		}
568 
569 		loaded = true;
570 
571 		return 0;
572 	}
573 
574 
575 	//	Save the character options.
576 	//	Return 0, or -1 on error.
577 	//
save(void)578 	int Character_Options::save(void)
579 	{
580 		if (!modified)
581 			return 0;
582 
583 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + the_file_name + "]");
584 
585 		if (parse_error)
586 		{
587 			JSON_IO::file_format_error(class_name_str);
588 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
589 		}
590 
591 		std::ofstream out_file(the_file_name);
592 		if (out_file)
593 		{
594 			out_file << std::setw(JSON_IO::get_json_indent()) << json_data << std::endl;
595 			modified = false;
596 			return 0;
597 		}
598 		else
599 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + the_file_name + "]", -1);
600 	}
601 
602 
603 	//	Return true if the specificed option exists for the character name
604 	//
exists(const char * var_name) const605 	bool Character_Options::exists(const char *var_name) const
606 	{
607 		if (!have_options_list())
608 			return false;
609 
610 		for (auto var : json_data[ini_options_list_str].items())
611 			if (var.value()[name_str] == var_name)
612 				return true;
613 
614 		return false;
615 	}
616 
617 
618 	//	Return true if the specificed option exists for the character name
619 	//
remove(const char * var_name)620 	void Character_Options::remove(const char *var_name)
621 	{
622 		if (!have_options_list())
623 			return;
624 
625 		// cannot get .erase() to work so do it the hard way....
626 		bool removed_var = false;
627 		json new_options_list = json::array();
628 		for (auto var : json_data[ini_options_list_str].items())
629 			if (var.value()[name_str] == var_name)
630 				removed_var = true;
631 			else
632 				new_options_list.push_back(var.value());
633 		if (removed_var)
634 		{
635 			json_data[ini_options_list_str] = new_options_list;
636 			modified = true;
637 		}
638 	}
639 
640 
641 	//	Get the named value, if not found or invalid, the default value is returned.
642 	//
get(const char * var_name,TheType default_value) const643 	template <class TheType> TheType Character_Options::get(const char *var_name, TheType default_value) const
644 	{
645 		if (!have_options_list())
646 			return default_value;
647 
648 		for (auto var : json_data[ini_options_list_str].items())
649 			if (var.value()[name_str] == var_name)
650 				return var.value()[value_str];
651 
652 		return default_value;
653 	}
654 
655 	//	Set the named value, creating the array if required
656 	//
set(const char * var_name,TheType value)657 	template <class TheType> void Character_Options::set(const char *var_name, TheType value)
658 	{
659 		modified = true;
660 
661 		if (!have_options_list())
662 			json_data[ini_options_list_str] = json::array();
663 
664 		for (auto var : json_data[ini_options_list_str].items())
665 			if (var.value()[name_str] == var_name)
666 			{
667 				var.value()[value_str] = value;
668 				return;
669 			}
670 		json new_value;
671 		new_value[name_str] = var_name;
672 		new_value[value_str] = value;
673 		json_data[ini_options_list_str].push_back(new_value);
674 		return;
675 	}
676 }
677 
678 
679 namespace JSON_IO_Client_State
680 {
681 	using json = nlohmann::json;
682 
683 	//	A Class to load and save the Client State.
684 	//	The state is items we need to save for the user
685 	//	but which are not options for them to set manually.
686 	//
687 	class Client_State
688 	{
689 		public:
Client_State(void)690 			Client_State(void) : parse_error(false) {}
691 			int load(const char *file_name);
692 			int save(const char *file_name);
693 			template <class TheType> TheType get(const char *section_name, const char *var_name, TheType default_var_value) const;
694 			template <class TheType> void set(const char *section_name, const char *var_name, TheType value);
695 		private:
696 			bool parse_error;		// there was an error populating the json object
697 			const char * class_name_str = "Client State";
698 			json state_read;
699 			json state_write;
700 	};
701 
702 
703 	//	Load the Client State.
704 	//	Return 0 on success or -1 on error.
705 	//
load(const char * file_name)706 	int Client_State::load(const char *file_name)
707 	{
708 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
709 
710 		std::ifstream in_file(file_name);
711 		if (!in_file)
712 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1);
713 
714 		try
715 		{
716 			in_file >> state_read;
717 		}
718 		catch (json::exception& e)
719 		{
720 			parse_error = true;
721 			JSON_IO::file_format_error(class_name_str);
722 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1);
723 		}
724 
725 		return 0;
726 	}
727 
728 
729 	//	Save the Client State.
730 	//	Return 0, or -1 on error.
731 	//
save(const char * file_name)732 	int Client_State::save(const char *file_name)
733 	{
734 		JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]");
735 
736 		if (parse_error)
737 		{
738 			JSON_IO::file_format_error(class_name_str);
739 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error.  Fix the problem first.", -1);
740 		}
741 
742 		std::ofstream out_file(file_name);
743 		if (out_file)
744 		{
745 			out_file << std::setw(JSON_IO::get_json_indent()) << state_write << std::endl;
746 			return 0;
747 		}
748 		else
749 			return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1);
750 	}
751 
752 
753 	//	Get the named value, if not found or invalid, the default value is returned.
754 	//
get(const char * section_name,const char * var_name,TheType default_value) const755 	template <class TheType> TheType Client_State::get(const char *section_name, const char *var_name, TheType default_value) const
756 	{
757 		if (state_read.contains(section_name) && state_read[section_name].contains(var_name))
758 			return state_read[section_name][var_name].get<TheType>();
759 		return default_value;
760 	}
761 
762 	//	Set the named value, creating the array if required
763 	//
set(const char * section_name,const char * var_name,TheType value)764 	template <class TheType> void Client_State::set(const char *section_name, const char *var_name, TheType value)
765 	{
766 		state_write[section_name][var_name] = value;
767 	}
768 
769 }
770 
771 
772 //	The instance of the manufacture recipe object.
773 static JSON_IO_Recipes::Recipes recipes;
774 
775 //	The instance of the quickspells object.
776 static JSON_IO_Quickspells::Quickspells quickspells;
777 
778 //	The instance of the counters object.
779 static JSON_IO_Counters::Counters counters;
780 
781 //	The instance of the channel colours object.
782 static JSON_IO_Channel_Colours::Channel_Colours channel_colours;
783 
784 //	The instance of the character options object.
785 static JSON_IO_Character_Options::Character_Options character_options;
786 
787 //	The instance of the Client State ojbect.
788 static JSON_IO_Client_State::Client_State cstate;
789 
790 //	The C interface
791 //
792 extern "C"
793 {
794 	// manufacture recipe functions
json_open_recipes(const char * file_name)795 	int json_open_recipes(const char *file_name)
796 		{ return recipes.open(file_name); }
json_load_recipes(recipe_entry * recipes_store,size_t max_recipes)797 	int json_load_recipes(recipe_entry *recipes_store, size_t max_recipes)
798 		{ return recipes.load(recipes_store, max_recipes); }
json_save_recipes(const char * file_name,recipe_entry * recipes_store,size_t num_recipes,int current_recipe)799 	int json_save_recipes(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe)
800 		{ return recipes.save(file_name, recipes_store, num_recipes, current_recipe); }
801 
802 	// quickspells funcitons
json_load_quickspells(const char * file_name,int * spell_ids,size_t max_num_spell_id)803 	int json_load_quickspells(const char *file_name, int *spell_ids, size_t max_num_spell_id)
804 		{ return quickspells.load(file_name, spell_ids, max_num_spell_id); }
json_save_quickspells(const char * file_name,Uint16 * spell_ids,size_t num_spell_id)805 	int json_save_quickspells(const char *file_name, Uint16 *spell_ids, size_t num_spell_id)
806 		{ return quickspells.save(file_name, spell_ids, num_spell_id); }
807 
808 	// counters functions
json_load_counters(const char * file_name,const char ** cat_str,int * entries,size_t num_categories,struct Counter ** the_counters)809 	int json_load_counters(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters)
810 		{ return counters.load(file_name, cat_str, entries, num_categories, the_counters); }
json_save_counters(const char * file_name,const char ** cat_str,const int * entries,size_t num_categories,const struct Counter ** the_counters)811 	int json_save_counters(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters)
812 		{ return counters.save(file_name, cat_str, entries, num_categories, the_counters); }
813 
814 	// channel colours
json_load_channel_colours(const char * file_name,channelcolor * the_channel_colours,size_t max_channel_colours)815 	int json_load_channel_colours(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours)
816 		{ return channel_colours.load(file_name, the_channel_colours, max_channel_colours); }
json_save_channel_colours(const char * file_name,const channelcolor * the_channel_colours,size_t max_channel_colours)817 	int json_save_channel_colours(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours)
818 		{ return channel_colours.save(file_name, the_channel_colours, max_channel_colours); }
819 
820 	// character options
json_character_options_set_file_name(const char * file_name)821 	void json_character_options_set_file_name(const char *file_name)
822 		{ character_options.set_file_name(file_name); }
json_character_options_load_file(void)823 	int json_character_options_load_file(void)
824 		{ return character_options.load(); }
json_character_options_save_file(void)825 	int json_character_options_save_file(void)
826 		{ return character_options.save(); }
json_character_options_exists(const char * var_name)827 	int json_character_options_exists(const char *var_name)
828 		{ return (character_options.exists(var_name)) ?1 :0; }
json_character_options_remove(const char * var_name)829 	void json_character_options_remove(const char *var_name)
830 		{ character_options.remove(var_name); }
json_character_options_get_int(const char * var_name,int default_value)831 	int json_character_options_get_int(const char *var_name, int default_value )
832 		{ return character_options.get(var_name, default_value); }
json_character_options_set_int(const char * var_name,int value)833 	void json_character_options_set_int(const char *var_name, int value)
834 		{ character_options.set(var_name, value); }
json_character_options_get_float(const char * var_name,float default_value)835 	float json_character_options_get_float(const char *var_name, float default_value )
836 		{ return character_options.get(var_name, default_value); }
json_character_options_set_float(const char * var_name,float value)837 	void json_character_options_set_float(const char *var_name, float value)
838 		{ character_options.set(var_name, value); }
json_character_options_get_bool(const char * var_name,int default_value)839 	int json_character_options_get_bool(const char *var_name, int default_value )
840 		{ return (character_options.get(var_name, default_value)) ?1 :0; }
json_character_options_set_bool(const char * var_name,int value)841 	void json_character_options_set_bool(const char *var_name, int value)
842 		{ character_options.set(var_name, static_cast<bool>(value)); }
843 
844 	// Client State
json_load_cstate(const char * file_name)845 	int json_load_cstate(const char *file_name)
846 		{ return cstate.load(file_name); }
json_save_cstate(const char * file_name)847 	int json_save_cstate(const char *file_name)
848 		{ return cstate.save(file_name); }
849 	// get/set int
json_cstate_get_int(const char * section_name,const char * var_name,int default_value)850 	int json_cstate_get_int(const char *section_name, const char *var_name, int default_value)
851 		{ return cstate.get(section_name, var_name, default_value); }
json_cstate_set_int(const char * section_name,const char * var_name,int value)852 	void json_cstate_set_int(const char *section_name, const char *var_name, int value)
853 		{ cstate.set(section_name, var_name, value); }
854 	// get/set unsigned int
json_cstate_get_unsigned_int(const char * section_name,const char * var_name,unsigned int default_value)855 	unsigned int json_cstate_get_unsigned_int(const char *section_name, const char *var_name, unsigned int default_value)
856 		{ return cstate.get(section_name, var_name, default_value); }
json_cstate_set_unsigned_int(const char * section_name,const char * var_name,unsigned int value)857 	void json_cstate_set_unsigned_int(const char *section_name, const char *var_name, unsigned int value)
858 		{ cstate.set(section_name, var_name, value); }
859 	// get/set float
json_cstate_get_float(const char * section_name,const char * var_name,float default_value)860 	float json_cstate_get_float(const char *section_name, const char *var_name, float default_value)
861 		{ return cstate.get(section_name, var_name, default_value); }
json_cstate_set_float(const char * section_name,const char * var_name,float value)862 	void json_cstate_set_float(const char *section_name, const char *var_name, float value)
863 		{ cstate.set(section_name, var_name, value); }
864 	// get_set bool
json_cstate_get_bool(const char * section_name,const char * var_name,int default_value)865 	int json_cstate_get_bool(const char *section_name, const char *var_name, int default_value)
866 		{ return ((cstate.get(section_name, var_name, static_cast<bool>(default_value))) ?1 :0); }
json_cstate_set_bool(const char * section_name,const char * var_name,int value)867 	void json_cstate_set_bool(const char *section_name, const char *var_name, int value)
868 		{ cstate.set(section_name, var_name, static_cast<bool>(value)); }
869 }
870