#include "IZ_config.h" const char* IZ_ConfigGetCommandlineOption(u8 argc, const char* argv[], const char* val) { size_t n = strlen(val); int c = argc; while (--c > 0) { if (!strncmp(argv[c], val, n)) { if (!*(argv[c] + n) && c < argc - 1) { /* coverity treats unchecked argv as "tainted" */ if (!argv[c + 1] || strlen(argv[c + 1]) > 1024) return NULL; return argv[c + 1]; } if (argv[c][n] == '=') return &argv[c][n + 1]; return argv[c] + n; } } return NULL; } typedef enum { IZ_CONFIG_SAVE_ITEM_ERROR = -1, IZ_CONFIG_SAVE_ITEM_OK, } IZ_ConfigSaveItemResult; #define IZ_CONFIG_REGISTER_INT_TYPE(ID, T) \ typedef bool IZ_ConfigValidate##ID(T); \ \ typedef T IZ_ConfigDeserialize##ID(const char*); \ \ typedef void IZ_ConfigSerialize##ID(T, const char[128]); \ \ void IZ_ConfigEnsureValid##ID(IZ_ConfigItem* item, T raw_value, T default_value) { \ T* dest = item->dest; \ if (item->validator) { \ IZ_ConfigValidate##ID* validate = item->validator; \ if (validate(raw_value)) { \ *dest = raw_value; \ return; \ } \ *dest = default_value; \ return; \ } \ *dest = raw_value; \ } \ \ void IZ_ConfigLoad##ID(IZ_ConfigItem* item, const char* config_path) { \ static T raw_value; \ static T default_value; \ default_value = *((T*) item->default_value); \ if (item->transformer.deserialize && item->transformer.serialize) { \ IZ_ConfigDeserialize##ID* deserialize = item->transformer.deserialize; \ IZ_ConfigSerialize##ID* serialize = item->transformer.serialize; \ const char serialized_default_value[128]; \ if (default_value) { \ serialize(default_value, serialized_default_value); \ } \ char buffer[128]; \ ini_gets(item->section, item->key, serialized_default_value, buffer, 128, config_path); \ raw_value = deserialize(buffer); \ } else { \ raw_value = ini_getl(item->section, item->key, default_value, config_path); \ } \ IZ_ConfigEnsureValid##ID(item, raw_value, default_value); \ } \ \ IZ_ConfigSaveItemResult IZ_ConfigSave##ID(IZ_ConfigItem* item, const char* config_path) { \ T dest = *((T*) item->dest); \ if (item->validator) { \ IZ_ConfigValidate##ID* validate = item->validator; \ if (!validate(dest)) { \ dest = *((const T*) item->default_value); \ } \ } \ \ if (item->transformer.deserialize && item->transformer.serialize) { \ IZ_ConfigSerialize##ID* serialize = item->transformer.serialize; \ const char serialized_value[128]; \ serialize(dest, serialized_value); \ if (!ini_puts(item->section, item->key, serialized_value, config_path)) { \ return -1; \ } \ return 0; \ } \ \ if (!ini_putl(item->section, item->key, dest, config_path)) { \ return -1; \ } \ \ return 0; \ } \ \ void IZ_ConfigOverride##ID(IZ_ConfigItem* item, u8 argc, const char* argv[]) { \ if (!item->cmdline_option) { \ return; \ } \ const char* cmdline_buffer; \ char* rest_of_string; \ static T dest; \ static T config_value; \ config_value = *((T*) item->dest); \ if ((cmdline_buffer = IZ_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { \ dest = strtol(cmdline_buffer, &rest_of_string, 10); \ if (strcmp(cmdline_buffer, rest_of_string) != 0) { \ IZ_ConfigEnsureValid##ID(item, dest, config_value); \ return; \ } \ } \ } IZ_CONFIG_REGISTER_INT_TYPE(U8, u8); IZ_CONFIG_REGISTER_INT_TYPE(U16, u16); IZ_CONFIG_REGISTER_INT_TYPE(I32, i32); typedef bool IZ_ConfigLoadParamsStringValidator(const char*); void IZ_ConfigEnsureValidString(IZ_ConfigItem* item, const char* buffer) { if (item->validator) { IZ_ConfigLoadParamsStringValidator* validator = item->validator; if (validator(buffer)) { memcpy_s(item->dest, item->dest_size, buffer, item->dest_size); return; } memcpy_s(item->dest, item->dest_size, item->default_value, item->dest_size); return; } memcpy_s(item->dest, item->dest_size, buffer, item->dest_size); } void IZ_ConfigLoadString(IZ_ConfigItem* item, const char* config_path) { char buffer[item->dest_size]; ini_gets(item->section, item->key, item->default_value, buffer, (i32) item->dest_size, config_path); IZ_ConfigEnsureValidString(item, buffer); } IZ_ConfigSaveItemResult IZ_ConfigSaveString(IZ_ConfigItem* item, const char* config_path) { const char* dest = (const char*) item->dest; if (item->validator) { IZ_ConfigLoadParamsStringValidator* validator = item->validator; if (!validator(dest)) { dest = (const char*) item->default_value; } } if (!ini_puts(item->section, item->key, dest, config_path)) { return IZ_CONFIG_SAVE_ITEM_ERROR; } return IZ_CONFIG_SAVE_ITEM_OK; } void IZ_ConfigOverrideString(IZ_ConfigItem* item, u8 argc, const char* argv[]) { if (!item->cmdline_option) { return; } const char* cmdline_buffer; if ((cmdline_buffer = IZ_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { IZ_ConfigEnsureValidString(item, cmdline_buffer); } } void IZ_ConfigLoad(IZ_ConfigItem item[], const char* config_path) { u8 i; for (i = 0; item[i].type != IZ_CONFIG_TYPE_VOID; i += 1) { switch (item[i].type) { case IZ_CONFIG_TYPE_STRING: IZ_ConfigLoadString(&item[i], config_path); break; case IZ_CONFIG_TYPE_U8: IZ_ConfigLoadU8(&item[i], config_path); break; case IZ_CONFIG_TYPE_U16: IZ_ConfigLoadU16(&item[i], config_path); break; case IZ_CONFIG_TYPE_I32: IZ_ConfigLoadI32(&item[i], config_path); default: break; } } } IZ_ConfigSaveResult IZ_ConfigSave(IZ_ConfigItem item[], const char* config_path) { u8 i; i64 problems = 0; for (i = 0; item[i].type != IZ_CONFIG_TYPE_VOID; i += 1) { i8 result = 0; switch (item[i].type) { case IZ_CONFIG_TYPE_STRING: result = IZ_ConfigSaveString(&item[i], config_path); break; case IZ_CONFIG_TYPE_U8: result = IZ_ConfigSaveU8(&item[i], config_path); break; case IZ_CONFIG_TYPE_U16: result = IZ_ConfigSaveU16(&item[i], config_path); break; case IZ_CONFIG_TYPE_I32: result = IZ_ConfigSaveI32(&item[i], config_path); default: break; } if (result < 0) { problems |= (1u << (i64) i); } } return -problems; } void IZ_ConfigOverride(IZ_ConfigItem item[], u8 argc, const char* argv[]) { u8 i; for (i = 0; item[i].type != IZ_CONFIG_TYPE_VOID; i += 1) { switch (item[i].type) { case IZ_CONFIG_TYPE_STRING: IZ_ConfigOverrideString(&item[i], argc, argv); break; case IZ_CONFIG_TYPE_U8: IZ_ConfigOverrideU8(&item[i], argc, argv); break; case IZ_CONFIG_TYPE_U16: IZ_ConfigOverrideU16(&item[i], argc, argv); break; case IZ_CONFIG_TYPE_I32: IZ_ConfigOverrideI32(&item[i], argc, argv); break; default: break; } } } IZ_ConfigInitializeResult IZ_ConfigInitialize(IZ_ConfigItem item[], const char* config_path, u8 argc, const char* argv[]) { IZ_ConfigLoad(item, config_path); IZ_ConfigSaveResult save_result = IZ_ConfigSave(item, config_path); if (save_result < 0) { return IZ_CONFIG_INITIALIZE_RESULT_ERROR; } IZ_ConfigOverride(item, argc, argv); if (save_result > 0) { return IZ_CONFIG_INITIALIZE_RESULT_WARNING; } return IZ_CONFIG_INITIALIZE_RESULT_OK; }