File: presets.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,716 kB
  • sloc: cpp: 595,001; ansic: 21,741; python: 1,174; sh: 457; makefile: 248; xml: 181
file content (525 lines) | stat: -rw-r--r-- 15,209 bytes parent folder | download | duplicates (2)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
#include "controlconfig/controlsconfig.h"
#include "controlconfig/presets.h"
#include "libs/jansson.h"
#include "parse/parselo.h"

#include <jansson.h>

using namespace io::presets;

const SCP_vector<std::pair<Section, const char*>> SectionMapping {
	std::pair<Section, const char*>(Section::Unnamed, nullptr),
	std::pair<Section, const char*>(Section::Actions, "actions"),
	std::pair<Section, const char*>(Section::Primary, "primary"),
	std::pair<Section, const char*>(Section::Secondary, "secondary"),
};

const char * lookupSectionName(Section id) {
	for (const auto& pair: SectionMapping) {
		if (pair.first == id) {
			return pair.second;
		}
	}

	return nullptr;
}

Section lookupSectionValue(const char* name) {
	Assertion(name != nullptr, "Key name must be a valid pointer!");

	for (auto& pair : SectionMapping) {
		if (!strcmp(pair.second, name)) {
			return pair.first;
		}
	}
	return Section::Invalid;
}

void load_preset_files(SCP_string clone) {
	SCP_vector<SCP_string> filelist;
	cf_get_file_list(filelist, CF_TYPE_PLAYER_BINDS, NOX("*.json"), CF_SORT_NAME, nullptr,
	                 CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);

	std::unique_ptr<PresetFileHandler> handler = nullptr;

	for (const auto &file : filelist) {
		CFILE* fp = cfopen((file + ".json").c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
						   CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);

		if (!fp) {
			mprintf(("PST => Unable to open '%s' for loading!\n", file.c_str()));
			// try next
			continue;
		}

		try {
			handler.reset(new PresetFileHandler(fp, true));
		} catch (const std::exception& e) {
			mprintf(("PST => Failed to open JSON: `%s`\n", e.what()));
			continue;
		}

		// Check the signature
		int pst_id = handler->readInt("signature");
		if (pst_id != PST_FILE_ID) {
			mprintf(("PST => Invalid header id for `%s`, skipping...\n", file.c_str()));
			continue;
		}

		// Version
		int version = handler->readInt("version");
		mprintf(("PST => Loading `%s` with version %i\n", file.c_str(), version));


		// Start reading in data
		CC_preset preset;
		preset.name = file;
		preset.type = Preset_t::pst;
		preset.bindings.resize(Control_config.size());

		size_t size;
		handler->beginArrayRead(Section::Actions, size);
		for (size_t i = 0; i < size; ++i, handler->nextArrayElement()) {
			SCP_string str;
			int id;

			//handler->beginSectionRead(Section::Unnamed);
			str = handler->readString("bind");
			id = ActionToVal(str.c_str());

			if (id < 0) {
				// Unknown bind, possibly custom.  Ignore for now
				handler->endSectionRead();	// Unnamed
				continue;
			}

			auto &item = preset.bindings[id];

			handler->beginSectionRead(Section::Primary);
			str = handler->readString("cid");
			auto cid = CIDToVal(str.c_str());

			str = handler->readString("flags");
			auto flags = CCFToVal(str.c_str());

			str = handler->readString("input");
			auto btn = InputToVal(cid, str.c_str());
			item.first.take(cid, btn, flags);

			handler->endSectionRead(); // Primary


			handler->beginSectionRead(Section::Secondary);
			str = handler->readString("cid");
			cid = CIDToVal(str.c_str());

			str = handler->readString("flags");
			flags = CCFToVal(str.c_str());

			str = handler->readString("input");
			btn = InputToVal(cid, str.c_str());
			item.second.take(cid, btn, flags);
			handler->endSectionRead(); // Secondary

			//handler->endSectionRead(); // Unnamed
		}
		handler->endArrayRead(); // Actions

		// Done with the file
		auto it = preset_find_duplicate(preset);

		// If we just cloned the file, then allow the duplicate. Just this once.
		if ((clone == preset.name) || (it == Control_config_presets.end())) {
			Control_config_presets.push_back(preset);

		} else if ((it->name != preset.name) || (it->type != Preset_t::pst)) {
			// Complain and ignore if the preset names or the type differs
			Warning(LOCATION, "PST => Preset '%s' is a duplicate of an existing preset, ignoring", preset.name.c_str());
		
		} // else, silent ignore
	}
}

bool preset_file_exists(SCP_string name) {
	if (name.find(".json") == std::string::npos) {
		name.append(".json");
	}

	return cf_exists(name.c_str(), CF_TYPE_PLAYER_BINDS) != 0;
}

bool delete_preset_file(CC_preset preset) {

	// can't remove the default preset!
	if (preset.name == "default") {
		return false;
	}

	auto it = control_config_get_current_preset();

	// can't remove the currently loaded preset either!
	if (preset.name == it->name) {
		return false;
	}

	SCP_string filename = preset.name + ".json";

	cf_delete(filename.c_str(), CF_TYPE_PLAYER_BINDS, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);

	// Reload the presets from file.
	Control_config_presets.resize(1);
	load_preset_files();

	return true;
}

bool save_preset_file(CC_preset preset, bool overwrite) {
	// Must have a name
	if (preset.name.empty()) {
		mprintf(("PST => Unable to save preset, missing name!\n"));
		return false;
	}
	
	SCP_string filename = preset.name + ".json";
	std::unique_ptr<PresetFileHandler> handler = nullptr;

	// Check if there's a file already
	CFILE* fp = cfopen(filename.c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
	                  CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
	
	if ((fp != nullptr) && !overwrite) {
		mprintf(("PST => Unable to save '%s', file already exists!\n", filename.c_str()));
		return false;
	}

	// Try opening file for write
	fp = cfopen(filename.c_str(), "w", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
					   CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);

	if (!fp) {
		mprintf(("PST => Unable to save '%s', unknown error!\n", filename.c_str()));
	}

	try {
		handler.reset(new PresetFileHandler(fp, false));
	} catch (const std::exception& e) {
		mprintf(("PST => Failed to open JSON: %s\n", e.what()));
		return false;
	}

	// Write header and version
	handler->writeInt("signature", PST_FILE_ID);
	handler->writeInt("version", PST_VERSION);

	mprintf(("PST => Saving %s with version %d...\n", filename.c_str(), (int)PST_VERSION));

	handler->beginArrayWrite(Section::Actions);
	for (int i = 0; static_cast<size_t>(i) < preset.bindings.size(); ++i) {
		const auto& item = preset.bindings[i];
		const auto& first = item.first;
		const auto& second = item.second;

		handler->beginSectionWrite(Section::Unnamed);
		handler->writeString("bind", ValToAction(i));

		handler->beginSectionWrite(Section::Primary);
		handler->writeString("cid",   ValToCID(first.get_cid()));
		handler->writeString("flags", ValToCCF(first.get_flags()));
		handler->writeString("input", ValToInput(first));
		handler->endSectionWrite(); // Primary

		handler->beginSectionWrite(Section::Secondary);
		handler->writeString("cid", ValToCID(second.get_cid()));
		handler->writeString("flags", ValToCCF(second.get_flags()));
		handler->writeString("input", ValToInput(second));
		handler->endSectionWrite(); // Secondary

		handler->endSectionWrite();	// Control_config[i]
	}
	handler->endArrayWrite(); // Actions

	handler->flush();

	return true;
}

SCP_vector<CC_preset>::iterator preset_find_duplicate(const CC_preset& new_preset) {
	SCP_vector<CC_preset>::iterator it = Control_config_presets.begin();	// NOLINT(modernize-use-auto)

	for (; it != Control_config_presets.end(); ++it) {
		size_t i;
		for (i = 0; i < it->bindings.size(); ++i) {
			if (new_preset.bindings[i] != it->bindings[i]) {
				// These two differ, check the next in the vector
				break;
			}
		}

		if (i == it->bindings.size()) {
			// These two are equal
			break;
		}
	}

	return it;
}

PresetFileHandler::PresetFileHandler(CFILE* cfp, bool reading) : _cfp(cfp) {
	Assertion(cfp != nullptr, "File pointer must be valid!");

	if (reading) {
		json_error_t error;
		_rootObj = json_load_cfile(cfp, 0, &error);

		if (!_rootObj) {
			SCP_string errorStr;
			sprintf(errorStr, "Error while reading pilot file! %d: %s", error.line, error.text);
			throw std::runtime_error(errorStr);
		}
	} else {
		_rootObj = json_object();
	}
	_currentEl = _rootObj;
	_elementStack.push_back(_currentEl);
};

PresetFileHandler::~PresetFileHandler() {
	json_decref(_rootObj);

	cfclose(_cfp);
	_cfp = nullptr;
};

bool PresetFileHandler::beginSectionRead(Section s) {
	const char * sectionName = lookupSectionName(s);
	ensureExists(sectionName);

	auto section = json_object_get(_currentEl, sectionName);
	if (json_typeof(section) != JSON_OBJECT) {
		Error(LOCATION, "Section must be a JSON object!");
		return false;
	}

	pushElement(section);

	return true;
};

void PresetFileHandler::beginSectionWrite(Section s) {
	auto key_name = lookupSectionName(s);

	json_t* obj = json_object();

	if (json_is_array(_currentEl)) {
		// Currently in an array, section must be unnamed
		Assertion(key_name == nullptr, "Elements of array may not be named sections!");
		json_array_append_new(_currentEl, obj);
	} else {
		Assertion(key_name != nullptr, "Section outside of arrays must be named!");
		json_object_set_new(_currentEl, key_name, obj);
	}
	pushElement(obj);
};

bool PresetFileHandler::beginArrayRead(Section s, size_t &size) {
	Assertion(_arrayIndex == INVALID_SIZE, "Array nesting is not supported yet!");

	const char * name = lookupSectionName(s);
	ensureExists(name);

	auto array = json_object_get(_currentEl, name);
	if (json_typeof(array) != JSON_ARRAY) {
		Error(LOCATION, "Expected an array for '%s' but it was a different type!", name);
		return false;
	}

	pushElement(array);
	size = json_array_size(array);

	if (size == 0) {
		// Nothing to do here, avoid calling nextArraySection since that assumes that there is at least one element
		return true;
	} // Else, advance to the first element in the array

	_arrayIndex = 0;
	nextArrayElement();

	return true;
}

void PresetFileHandler::beginArrayWrite(Section s) {
	const char * name = lookupSectionName(s);
	auto array = json_array();

	if (json_is_array(_currentEl)) {
		// We are in an array, section must be unnamed
		Assertion(name == nullptr, "Inside an array there can be no named section!");
		json_array_append_new(_currentEl, array);
	} else {
		Assertion(name != nullptr, "Section outside of arrays must be named!");
		json_object_set_new(_currentEl, name, array);
	}
	pushElement(array);
}

void PresetFileHandler::endSectionRead() {
	Assertion(json_typeof(_currentEl) == JSON_OBJECT, "Current element for section reading is not an object!");

	popElement();
};

void PresetFileHandler::endSectionWrite() {
	Assertion(json_is_object(_currentEl), "Section ended while not in a section!");

	popElement();
};

void PresetFileHandler::endArrayRead() {
	// First, remove the element we are currently in if it exists
	if (json_typeof(_currentEl) != JSON_ARRAY) {
		popElement();
	}

	Assertion(json_typeof(_currentEl) == JSON_ARRAY, "Current element must be an array!");

	// TODO Sections are straightforward, but Arrays may need special consideration re: index.
	popElement();
	_arrayIndex = INVALID_SIZE;
};

void PresetFileHandler::endArrayWrite() {
	Assertion(json_is_array(_currentEl), "Array ended while not in an array!");

	popElement();
};

void PresetFileHandler::ensureExists(const char* name) {
	if (json_typeof(_currentEl) != JSON_OBJECT) {
		Error(LOCATION, "JSON reading requires a value with name '%s' but the current element is not an object!", name);
	}
	if (json_object_get(_currentEl, name) == nullptr) {
		Error(LOCATION, "JSON reading requires a value with name '%s' but there is no such value!", name);
	}
}

void PresetFileHandler::ensureNotExists(const char* name) {
	// Stuff debug checks into booleans, otherwise clang will trigger a false positive for static method with just the asserts
	// Make sure we're in an element that can support keys
	bool supports_keys = json_is_object(_currentEl);

	// Make sure we don't overwrite previous values
	bool element_is_unique = json_object_get(_currentEl, name) == nullptr;

	Assertion(supports_keys, "Currently not in an element that supports keys!");
	Assertion(element_is_unique, "Entry with name %s already exists!", name);
}

bool PresetFileHandler::exists(const char* name) {
	if (json_typeof(_currentEl) != JSON_OBJECT) {
		return false;
	}
	if (json_object_get(_currentEl, name) == nullptr) {
		return false;
	}

	return true;
}

void PresetFileHandler::flush() {
	Assertion(_elementStack.size() == 1, "Not all sections or arrays have been ended!");

	json_dump_cfile(_rootObj, _cfp, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
}

void PresetFileHandler::nextArrayElement() {
	Assertion(_arrayIndex != INVALID_SIZE, "Array index must be valid for this function!");
	bool in_section = json_is_object(_currentEl);

	if (in_section) {
		// We have to pop the previous section first
		popElement();

		Assertion(json_typeof(_currentEl) == JSON_ARRAY, "The previous element should have been an array!");
	}

	auto max = json_array_size(_currentEl);
	Assertion(_arrayIndex <= max, "Invalid array index detected!");

	// Silently ignore if we are one past the last element since this function is used in a for loop where this function
	// is executed one time after the index is incremented past the last element
	if (_arrayIndex == max) {
		// Increment the index so we catch usage errors the next time someone tries to call this function
		++_arrayIndex;
		return;
	}

	// We use the current array index and then increment it to avoid skipping the first element
	auto el = json_array_get(_currentEl, _arrayIndex);
	++_arrayIndex;

	pushElement(el);
}

json_t * PresetFileHandler::popElement() {
	Assertion(_elementStack.size() > 1, "Element stack may not get smaller than one element!");

	_elementStack.pop_back();
	_currentEl = _elementStack.back();
	return _currentEl;
}

void PresetFileHandler::pushElement(json_t* el) {
	Assertion(el != nullptr, "Invalid JSON element pointer passed!");

	_currentEl = el;
	_elementStack.push_back(_currentEl);
}

SCP_string PresetFileHandler::readString(const char* key) {
	auto el = json_object_get(_currentEl, key);

	if (el == nullptr || json_typeof(el) != JSON_STRING) {
		Error(LOCATION, "JSON element %s must be a string but it is not valid!", key);
		return SCP_string();
	}
	auto json_str = json_string_value(el);
	SCP_string val;
	val.assign(json_str, json_str + json_string_length(el));

	return val;
}

int PresetFileHandler::readInt(const char* key) {
	auto el = json_object_get(_currentEl, key);

	if (el == nullptr || json_typeof(el) != JSON_INTEGER) {
		Error(LOCATION, "JSON element %s must be an integer but it is not valid!", key);
		return 0;
	}

	return static_cast<int>(json_integer_value(el));
}

void PresetFileHandler::writeString(const char* key, SCP_string s) {
	ensureNotExists(key);
	json_t *jstr = nullptr;

	if (!s.empty()) {
		// if this string isn't proper UTF-8, try to convert it
		if (utf8::find_invalid(s.begin(), s.end()) != s.end()) {
			SCP_string buffer;
			coerce_to_utf8(buffer, s.c_str());
			jstr = json_string(buffer.c_str());
		} else {
			jstr = json_string(s.c_str());
		}
	}

	json_object_set_new(_currentEl, key, jstr);
};

void PresetFileHandler::writeInt(const char* key, int val) {
	ensureNotExists(key);

	json_object_set_new(_currentEl, key, json_integer(val));
}