56 errors_addf(
"%s.%s: invalid value \"%s\"", section, key, value);
60 errors_addf(
"%s: invalid value \"%s\"", key, value);
64 if (!yyjson_is_obj(buttons)) {
74 yyjson_obj_foreach(buttons, idx, max, key, val) {
75 const char *key_str = yyjson_get_str(key);
79 errors_addf(
"gamepad.buttons.%s: unknown SDL input", key_str);
83 const char *value = yyjson_get_str(val);
85 errors_addf(
"gamepad.buttons.%s: expected string value", key_str);
91 errors_addf(
"gamepad.buttons.%s: invalid action \"%s\"", key_str, value);
100 if (!yyjson_is_arr(disabled_mods)) {
109 yyjson_arr_foreach(disabled_mods, idx, max, val) {
110 const char *mod_filename = yyjson_get_str(val);
111 if (!mod_filename || !mod_filename[0]) {
112 errors_addf(
"modding.disabled_mods[%zu]: expected DLL filename", idx);
118 "modding.disabled_mods[%zu]: could not store \"%s\"",
127 yyjson_val *version = yyjson_obj_get(root,
"schema_major_version");
129 errors_addf(
"%s: missing required schema_major_version", filename);
134 if (!yyjson_is_int(version)) {
135 errors_addf(
"%s.schema_major_version: expected integer value", filename);
140 const int64_t parsed_version = yyjson_get_int(version);
141 if (parsed_version < INT_MIN || parsed_version > INT_MAX) {
142 errors_addf(
"%s.schema_major_version: integer value out of range", filename);
147 const int schema_major_version = (int)parsed_version;
153 "%s.schema_major_version: unsupported major version %d (expected %d)",
155 schema_major_version,
167 if (!config || !spec || !val) {
171 char *
const field = ((
char *)config) + spec->
offset;
174 if (!yyjson_is_bool(val)) {
178 *(
bool *)field = yyjson_get_bool(val);
182 if (!yyjson_is_int(val)) {
186 const int64_t parsed = yyjson_get_int(val);
187 if (parsed < INT_MIN || parsed > INT_MAX) {
191 *(
int *)field = (
int)parsed;
195 if (!yyjson_is_num(val)) {
199 const double parsed = yyjson_get_num(val);
200 if (!isfinite(parsed) || parsed < -(
double)FLT_MAX || parsed > (
double)FLT_MAX) {
204 *(
float *)field = (
float)parsed;
209 if (yyjson_is_null(val)) {
213 if (!yyjson_is_str(val)) {
220 if (!yyjson_is_str(val)) {
229 if (!yyjson_is_obj(obj)) {
238 yyjson_obj_foreach(obj, idx, max, key, val) {
239 if (yyjson_is_obj(val)) {
243 const char *key_str = yyjson_get_str(key);
246 if (!yyjson_is_arr(val)) {
262 if (!filename || !filename[0]) {
272 yyjson_doc *doc = yyjson_read_file(filename, 0,
NULL, &err);
275 if (err.code == YYJSON_READ_ERROR_FILE_OPEN) {
276 DTTR_LOG_WARN(
"File '%s' not found. Creating it from defaults.", filename);
279 "Could not create default config %s; continuing with built-in "
288 if (err.code == YYJSON_READ_ERROR_EMPTY_CONTENT) {
289 DTTR_LOG_WARN(
"File '%s' is empty. Using defaults.", filename);
293 DTTR_LOG_ERROR(
"JSON parse failed: %s at position %zu", err.msg, err.pos);
294 errors_addf(
"Failed to parse %s (%s at position %zu)", filename, err.msg, err.pos);
299 yyjson_val *root = yyjson_doc_get_root(doc);
301 yyjson_doc_free(doc);
308 yyjson_val *modding = yyjson_obj_get(root,
"modding");
312 yyjson_val *gamepad = yyjson_obj_get(root,
"gamepad");
313 if (yyjson_is_obj(gamepad)) {
318 yyjson_doc_free(doc);
328 return yyjson_mut_obj_add_strcpy(doc, obj, key, value ? value :
"");
344 return objects->
root;
352 return objects->
audio;
370 const char *
const field = ((
const char *)config) + spec->
offset;
406 const char *
const field = ((
const char *)config) + spec->
offset;
409 return yyjson_mut_obj_add_bool(doc, obj, spec->
key, *(
const bool *)field);
411 return yyjson_mut_obj_add_int(doc, obj, spec->
key, *(
const int *)field);
413 return yyjson_mut_obj_add_real(doc, obj, spec->
key, *(
const float *)field);
430 for (
int i = 0; i <
count; i++) {
442 yyjson_mut_val *modding,
445 yyjson_mut_val *disabled_mods = yyjson_mut_obj_add_arr(doc, modding,
"disabled_mods");
446 if (!disabled_mods) {
451 if (!yyjson_mut_arr_add_strcpy(doc, disabled_mods, config->
disabled_mods[i])) {
461 yyjson_mut_val *buttons,
484 if (!filename || !config) {
489 yyjson_mut_doc *doc = yyjson_mut_doc_new(
NULL);
494 yyjson_mut_val *root = yyjson_mut_obj(doc);
499 yyjson_mut_doc_set_root(doc, root);
503 .graphics = yyjson_mut_obj_add_obj(doc, root,
"graphics"),
504 .audio = yyjson_mut_obj_add_obj(doc, root,
"audio"),
505 .modding = yyjson_mut_obj_add_obj(doc, root,
"modding"),
506 .gamepad = yyjson_mut_obj_add_obj(doc, root,
"gamepad"),
513 yyjson_mut_val *buttons = yyjson_mut_obj_add_obj(doc, objects.
gamepad,
"buttons");
523 yyjson_write_err err;
524 ok = yyjson_mut_write_file(
527 YYJSON_WRITE_PRETTY | YYJSON_WRITE_NEWLINE_AT_END,
532 DTTR_LOG_ERROR(
"Failed to write config %s: %s", filename, err.msg);
537 yyjson_mut_doc_free(doc);
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD count
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
static void config_clear_button_map(int *map)
bool config_parse_game_action(const char *value, int *out_value)
bool config_apply_entry(DTTR_Config *config, const char *section, const char *key, const char *value)
const char * config_format_graphics_api(DTTR_GraphicsApi api)
const char * config_format_vertex_precision(DTTR_VertexPrecision precision)
const char * config_format_scaling_fit(DTTR_ScalingMode mode)
const DTTR_ConfigFieldSpec * config_schema_find(const char *section, const char *key)
const char * config_format_minidump_type(DTTR_MinidumpType type)
@ CONFIG_VERTEX_PRECISION
const char * config_format_present_filter(SDL_GPUFilter filter)
const char * config_format_gamepad_axis(int axis)
const char * config_format_game_action(int action)
const char * config_format_gamepad_source(int source)
static bool config_sections_match(const char *lhs, const char *rhs)
bool config_parse_gamepad_source(const char *value, int *out_value)
const char * config_format_scaling_method(DTTR_ScalingMethod method)
const char * config_format_log_level(int level)
bool DTTR_Config_SetModEnabled(DTTR_Config *config, const char *mod_filename, bool enabled)
void DTTR_Config_SetDefaults(DTTR_Config *config)
Resets a config object to built-in defaults.
#define DTTR_GAMEPAD_SOURCE_COUNT
#define DTTR_GAMEPAD_MAPPING_NONE
int DTTR_Config_SchemaCount()
const DTTR_ConfigFieldSpec * DTTR_Config_SchemaGet(int index)
#define DTTR_CONFIG_SCHEMA_MAJOR_VERSION
#define DTTR_LOG_WARN(...)
#define DTTR_LOG_ERROR(...)
static game_data_source source
static void errors_addf(const char *fmt,...)
static bool config_add_gamepad_buttons(yyjson_mut_doc *doc, yyjson_mut_val *buttons, const DTTR_Config *config)
static yyjson_mut_val * config_object_for_section(const config_json_objects *objects, const char *section)
static bool config_add_schema_field(yyjson_mut_doc *doc, const config_json_objects *objects, const DTTR_Config *config, const DTTR_ConfigFieldSpec *spec)
static void errors_add_invalid_value(const char *section, const char *key, const char *value)
static const char * config_format_field_string(const DTTR_Config *config, const DTTR_ConfigFieldSpec *spec)
static void config_apply_disabled_mods(DTTR_Config *config, yyjson_val *disabled_mods)
static bool config_add_schema_fields(yyjson_mut_doc *doc, const config_json_objects *objects, const DTTR_Config *config)
static bool obj_add_strcpy(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *value)
static void config_apply_buttons(DTTR_Config *config, yyjson_val *buttons)
const char * DTTR_Config_LastError()
Returns details from the most recent config load failure, or NULL when none exist.
static void errors_clear()
static bool validate_schema_major_version(yyjson_val *root, const char *filename)
static void apply_section(yyjson_val *obj, const char *section)
bool DTTR_Config_Save(const char *filename, const DTTR_Config *config)
Saves config values back to a strict JSON file.
bool DTTR_Config_Load(const char *filename)
Loads config values from a strict JSON file into the global config object.
static bool errors_show()
static bool config_add_disabled_mods(yyjson_mut_doc *doc, yyjson_mut_val *modding, const DTTR_Config *config)
static bool config_apply_json_value(DTTR_Config *config, const DTTR_ConfigFieldSpec *spec, yyjson_val *val)
DTTR_ConfigValueType value_type
int gamepad_button_map[DTTR_GAMEPAD_SOURCE_COUNT]
char disabled_mods[DTTR_CONFIG_DISABLED_MODS_MAX][MAX_PATH]
yyjson_mut_val * graphics