소스 검색

Organize library

Separate types from main definition.
master
부모
커밋
83381f4a5f
7개의 변경된 파일365개의 추가작업 그리고 271개의 파일을 삭제
  1. +7
    -3
      CMakeLists.txt
  2. +0
    -192
      ini-config.c
  3. +98
    -76
      ini-config.h
  4. +93
    -0
      types/int.c
  5. +54
    -0
      types/int.h
  6. +99
    -0
      types/string.c
  7. +14
    -0
      types/string.h

+ 7
- 3
CMakeLists.txt 파일 보기

@@ -7,6 +7,10 @@ include_directories(
"${CMAKE_HOME_DIRECTORY}/subprojects/minIni/dev"
)

add_executable(ini_config
ini-config.c
ini-config.h)
add_executable(
ini_config
ini-config.c
ini-config.h
int.c
int.h
)

+ 0
- 192
ini-config.c 파일 보기

@@ -21,195 +21,3 @@ const char* INI_ConfigGetCommandlineOption(uint8_t argc, const char* argv[], con

return NULL;
}

#define INI_CONFIG_REGISTER_INT_TYPE(ID, T) \
typedef bool INI_ConfigValidate##ID(T); \
\
typedef T INI_ConfigDeserialize##ID(const char*); \
\
typedef void INI_ConfigSerialize##ID(T, const char[128]); \
\
void INI_ConfigEnsureValid##ID(INI_ConfigItem* item, T raw_value, T default_value) { \
T* dest = item->dest; \
if (item->validator) { \
INI_ConfigValidate##ID* validate = item->validator; \
if (validate(raw_value)) { \
*dest = raw_value; \
return; \
} \
*dest = default_value; \
return; \
} \
*dest = raw_value; \
} \
\
void INI_ConfigLoad##ID(INI_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) { \
INI_ConfigDeserialize##ID* deserialize = item->transformer.deserialize; \
INI_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); \
} \
INI_ConfigEnsureValid##ID(item, raw_value, default_value); \
} \
\
INI_ConfigSaveItemResult INI_ConfigSave##ID(INI_ConfigItem* item, const char* config_path) { \
T dest = *((T*) item->dest); \
if (item->validator) { \
INI_ConfigValidate##ID* validate = item->validator; \
if (!validate(dest)) { \
dest = *((const T*) item->default_value); \
} \
} \
\
if (item->transformer.deserialize && item->transformer.serialize) { \
INI_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 INI_ConfigOverride##ID(INI_ConfigItem* item, uint8_t 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 = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { \
dest = strtol(cmdline_buffer, &rest_of_string, 10); \
if (strcmp(cmdline_buffer, rest_of_string) != 0) { \
INI_ConfigEnsureValid##ID(item, dest, config_value); \
return; \
} \
} \
}

INI_CONFIG_REGISTER_INT_TYPE(U8, uint8_t);
INI_CONFIG_REGISTER_INT_TYPE(U16, uint16_t);
INI_CONFIG_REGISTER_INT_TYPE(U32, uint32_t);
INI_CONFIG_REGISTER_INT_TYPE(I8, int8_t);
INI_CONFIG_REGISTER_INT_TYPE(I16, int16_t);
INI_CONFIG_REGISTER_INT_TYPE(I32, int32_t);

typedef bool INI_ConfigLoadParamsStringValidator(const char*);

void INI_ConfigEnsureValidString(INI_ConfigItem* item, const char* buffer) {
if (item->validator) {
INI_ConfigLoadParamsStringValidator* validator = item->validator;
if (validator(buffer)) {
memcpy(item->dest, buffer, item->type.size);
return;
}
memcpy(item->dest, item->default_value, item->type.size);
return;
}
memcpy(item->dest, buffer, item->type.size);
}

void INI_ConfigLoadString(INI_ConfigItem* item, const char* config_path) {
char buffer[item->type.size];
ini_gets(item->section, item->key, item->default_value, buffer, (int32_t) item->type.size, config_path);
INI_ConfigEnsureValidString(item, buffer);
}

INI_ConfigSaveItemResult INI_ConfigSaveString(INI_ConfigItem* item, const char* config_path) {
const char* dest = (const char*) item->dest;
if (item->validator) {
INI_ConfigLoadParamsStringValidator* validator = item->validator;
if (!validator(dest)) {
dest = (const char*) item->default_value;
}
}

if (!ini_puts(item->section, item->key, dest, config_path)) {
return INI_CONFIG_SAVE_ITEM_ERROR;
}

return INI_CONFIG_SAVE_ITEM_OK;
}

void INI_ConfigOverrideString(INI_ConfigItem* item, uint8_t argc, const char* argv[]) {
if (!item->cmdline_option) {
return;
}
const char* cmdline_buffer;
if ((cmdline_buffer = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) {
INI_ConfigEnsureValidString(item, cmdline_buffer);
}
}

void INI_ConfigLoad(INI_ConfigItem item[], const char* config_path) {
uint8_t i;
for (i = 0; item[i].type.size > 0; i += 1) {
if (!item[i].type.load) {
continue;
}
item[i].type.load(&item[i], config_path);
}
}

INI_ConfigSaveResult INI_ConfigSave(INI_ConfigItem item[], const char* config_path) {
uint8_t i;
int32_t problems = 0;
for (i = 0; item[i].type.size > 0; i += 1) {
int32_t result = item[i].type.save ? item[i].type.save(&item[i], config_path) : 0;

if (result < 0) {
problems |= (1 << (int32_t) i);
}
}

return -problems;
}

void INI_ConfigOverride(INI_ConfigItem item[], uint8_t argc, const char* argv[]) {
uint8_t i;
for (i = 0; item[i].type.size > 0; i += 1) {
if (!item[i].type.override) {
continue;
}

item[i].type.override(&item[i], argc, argv);
// TODO specify command-line arg external from config item?

}
}

INI_ConfigInitializeResult INI_ConfigInitialize(INI_ConfigItem item[], const char* config_path, uint8_t argc, const char* argv[]) {
INI_ConfigLoad(item, config_path);
INI_ConfigSaveResult save_result = INI_ConfigSave(item, config_path);
if (save_result < 0) {
//INI_LogError("config", "Sync failed! Result: %u", save_result);
return INI_CONFIG_INITIALIZE_RESULT_ERROR;
}
INI_ConfigOverride(item, argc, argv);
if (save_result > 0) {
//INI_LogWarn(false, "config", "Sync encountered issues. Result: %u", save_result);
return INI_CONFIG_INITIALIZE_RESULT_WARNING;
}
//INI_LogInfo(INI_LOG_CATEGORY_GLOBAL, "config", "Sync successful.");
return INI_CONFIG_INITIALIZE_RESULT_OK;
}

+ 98
- 76
ini-config.h 파일 보기

@@ -7,48 +7,138 @@
#include <stdbool.h>
#include <minIni.h>

/**
* Struct for transformer functions used for properly loading and saving the config item's value.
*/
typedef struct {
/**
* Function that formats the value from memory to a value that is write-friendly to the config file.
*/
void* serialize;
/**
* Function that formats the value from file to a value that is read-friendly to memory.
*/
void* deserialize;
} INI_ConfigTransformer;
} INI_ConfigTransformer; // TODO: should we unify this with INI_ConfigType?

struct INI_ConfigItem;

/**
* Function for loading a config item value from file to memory.
*/
typedef void INI_ConfigTypeLoad(struct INI_ConfigItem*, const char*);

/**
* Result enum for saving config items.
*/
typedef enum {
/**
* Result returned when saving a config item was not successful.
*/
INI_CONFIG_SAVE_ITEM_ERROR = -1,

/**
* Result returned when saving a config item was successful.
*/
INI_CONFIG_SAVE_ITEM_OK,
} INI_ConfigSaveItemResult;

/**
* Function for saving a config item value from memory to file.
*/
typedef INI_ConfigSaveItemResult INI_ConfigTypeSave(struct INI_ConfigItem*, const char*);

/**
* Function for retrieving a config item value from the command-line to memory.
*/
typedef void INI_ConfigTypeOverride(struct INI_ConfigItem*, uint8_t, const char*[]);

/**
* Struct for the config item type.
*/
typedef struct {
/**
* Size of the corresponding in-memory value of the config item.
*/
size_t size;
/**
* Load function.
* @see INI_ConfigTypeLoad
*/
INI_ConfigTypeLoad* load;
/**
* Save function.
* @see INI_ConfigTypeSave
*/
INI_ConfigTypeSave* save;
/**
* Override function.
* @see INI_ConfigTypeOverride
*/
INI_ConfigTypeOverride* override;
} INI_ConfigType;

/**
* Struct for the config item, which occuptes a single key in a specific section of the config INI file.
*/
typedef struct INI_ConfigItem {
/**
* Type of the config item.
*/
INI_ConfigType type;
/**
* Section where this config item can be found.
*/
const char* section;
/**
* Key where this config item value is serialized and stored.
*/
const char* key;
const char* cmdline_option;
/**
* Command-line option for overriding this config item's value.
*/
const char* cmdline_option; // TODO: should we extract commandline parsing logic?
/**
* Default value of the config item, when the value could not be read from the config file.
*/
const void* default_value;
/**
* Validator function for the config item's value.
*/
void* validator;
/**
* Transformer functions.
* @see INI_ConfigTransformer
*/
INI_ConfigTransformer transformer;
/**
* The memory address where the config item value will reside. This property should allow storing the amount of butes
* specified under `type.size`.
*/
void* dest;
} INI_ConfigItem;

void INI_ConfigGetDefaultPath(char*, size_t);

/**
* Retrieves the value from a command-line option.
* @return The string value from the command-line option.
*/
const char* INI_ConfigGetCommandlineOption(uint8_t, const char*[], const char*);

/**
* Result enum for initializing config items.
*/
typedef enum {
/**
* Result returned when initializing all config items was not successful.
*/
INI_CONFIG_INITIALIZE_RESULT_ERROR = -1,
/**
* Result returned when initializing all config items was successful.
*/
INI_CONFIG_INITIALIZE_RESULT_OK,
/**
* Result returned when initializing some config items was successful.
*/
INI_CONFIG_INITIALIZE_RESULT_WARNING
} INI_ConfigInitializeResult;

@@ -58,78 +148,10 @@ typedef int32_t INI_ConfigSaveResult;

INI_ConfigSaveResult INI_ConfigSave(INI_ConfigItem[], const char*);

void INI_ConfigLoadU8(INI_ConfigItem*, const char*);
void INI_ConfigLoadU16(INI_ConfigItem*, const char*);
void INI_ConfigLoadU32(INI_ConfigItem*, const char*);
void INI_ConfigLoadI8(INI_ConfigItem*, const char*);
void INI_ConfigLoadI16(INI_ConfigItem*, const char*);
void INI_ConfigLoadI32(INI_ConfigItem*, const char*);
void INI_ConfigLoadString(INI_ConfigItem*, const char*);

INI_ConfigSaveItemResult INI_ConfigSaveU8(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveU16(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveU32(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveI8(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveI16(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveI32(INI_ConfigItem*, const char*);
INI_ConfigSaveItemResult INI_ConfigSaveString(INI_ConfigItem*, const char*);

void INI_ConfigOverrideU8(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideU16(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideU32(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideI8(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideI16(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideI32(INI_ConfigItem*, uint8_t, const char*[]);
void INI_ConfigOverrideString(INI_ConfigItem*, uint8_t, const char*[]);

#define INI_CONFIG_TYPE_U8 (INI_ConfigType) { \
.size = sizeof(uint8_t), \
.load = INI_ConfigLoadU8, \
.save = INI_ConfigSaveU8, \
.override = INI_ConfigOverrideU8, \
}

#define INI_CONFIG_TYPE_U16 (INI_ConfigType) { \
.size = sizeof(uint16_t), \
.load = INI_ConfigLoadU16, \
.save = INI_ConfigSaveU16, \
.override = INI_ConfigOverrideU16, \
}

#define INI_CONFIG_TYPE_U32 (INI_ConfigType) { \
.size = sizeof(uint32_t), \
.load = INI_ConfigLoadU32, \
.save = INI_ConfigSaveU32, \
.override = INI_ConfigOverrideU32, \
}

#define INI_CONFIG_TYPE_I8 (INI_ConfigType) { \
.size = sizeof(int8_t), \
.load = INI_ConfigLoadI8, \
.save = INI_ConfigSaveI8, \
.override = INI_ConfigOverrideI8, \
}

#define INI_CONFIG_TYPE_I16 (INI_ConfigType) { \
.size = sizeof(int16_t), \
.load = INI_ConfigLoadI16, \
.save = INI_ConfigSaveI16, \
.override = INI_ConfigOverrideI16, \
}

#define INI_CONFIG_TYPE_I32 (INI_ConfigType) { \
.size = sizeof(int32_t), \
.load = INI_ConfigLoadI32, \
.save = INI_ConfigSaveI32, \
.override = INI_ConfigOverrideI32, \
}

#define INI_CONFIG_TYPE_FNS_STRING(X) (INI_ConfigType) { \
.size = (sizeof(char) * X), \
.load = INI_ConfigLoadString, \
.save = INI_ConfigSaveString, \
.override = INI_ConfigOverrideString, \
}
#define INI_CONFIG_DECLARE_TYPE(ID) \
INI_ConfigTypeLoad INI_ConfigLoad##ID; \
INI_ConfigTypeSave INI_ConfigSave##ID; \
INI_ConfigTypeOverride INI_ConfigOverride##ID

#define INI_CONFIG_TRANSFORMER_NONE (INI_ConfigTransformer) { \
.serialize = NULL, \


+ 93
- 0
types/int.c 파일 보기

@@ -0,0 +1,93 @@
#include "int.h"

#define INI_CONFIG_IMPLEMENT_INT_TYPE(ID, T) \
typedef bool INI_ConfigValidate##ID(T); \
\
typedef T INI_ConfigDeserialize##ID(const char*); \
\
typedef void INI_ConfigSerialize##ID(T, const char[128]); \
\
void INI_ConfigEnsureValid##ID(INI_ConfigItem* item, T raw_value, T default_value) { \
T* dest = item->dest; \
if (item->validator) { \
INI_ConfigValidate##ID* validate = item->validator; \
if (validate(raw_value)) { \
*dest = raw_value; \
return; \
} \
*dest = default_value; \
return; \
} \
*dest = raw_value; \
} \
\
void INI_ConfigLoad##ID(INI_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) { \
INI_ConfigDeserialize##ID* deserialize = item->transformer.deserialize; \
INI_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); \
} \
INI_ConfigEnsureValid##ID(item, raw_value, default_value); \
} \
\
INI_ConfigSaveItemResult INI_ConfigSave##ID(INI_ConfigItem* item, const char* config_path) { \
T dest = *((T*) item->dest); \
if (item->validator) { \
INI_ConfigValidate##ID* validate = item->validator; \
if (!validate(dest)) { \
dest = *((const T*) item->default_value); \
} \
} \
\
if (item->transformer.deserialize && item->transformer.serialize) { \
INI_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 INI_ConfigOverride##ID(INI_ConfigItem* item, uint8_t 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 = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { \
dest = strtol(cmdline_buffer, &rest_of_string, 10); \
if (strcmp(cmdline_buffer, rest_of_string) != 0) { \
INI_ConfigEnsureValid##ID(item, dest, config_value); \
return; \
} \
} \
}

INI_CONFIG_IMPLEMENT_INT_TYPE(U8, uint8_t);
INI_CONFIG_IMPLEMENT_INT_TYPE(U16, uint16_t);
INI_CONFIG_IMPLEMENT_INT_TYPE(U32, uint32_t);
INI_CONFIG_IMPLEMENT_INT_TYPE(I8, int8_t);
INI_CONFIG_IMPLEMENT_INT_TYPE(I16, int16_t);
INI_CONFIG_IMPLEMENT_INT_TYPE(I32, int32_t);

+ 54
- 0
types/int.h 파일 보기

@@ -0,0 +1,54 @@
#ifndef INI_CONFIG_TYPES_INT_H
#define INI_CONFIG_TYPES_INT_H

#include "ini-config.h"

INI_CONFIG_DECLARE_TYPE(U8);
#define INI_CONFIG_TYPE_U8 (INI_ConfigType) { \
.size = sizeof(uint8_t), \
.load = INI_ConfigLoadU8, \
.save = INI_ConfigSaveU8, \
.override = INI_ConfigOverrideU8, \
}

INI_CONFIG_DECLARE_TYPE(U16);
#define INI_CONFIG_TYPE_U16 (INI_ConfigType) { \
.size = sizeof(uint16_t), \
.load = INI_ConfigLoadU16, \
.save = INI_ConfigSaveU16, \
.override = INI_ConfigOverrideU16, \
}

INI_CONFIG_DECLARE_TYPE(U32);
#define INI_CONFIG_TYPE_U32 (INI_ConfigType) { \
.size = sizeof(uint32_t), \
.load = INI_ConfigLoadU32, \
.save = INI_ConfigSaveU32, \
.override = INI_ConfigOverrideU32, \
}

INI_CONFIG_DECLARE_TYPE(I8);
#define INI_CONFIG_TYPE_I8 (INI_ConfigType) { \
.size = sizeof(int8_t), \
.load = INI_ConfigLoadI8, \
.save = INI_ConfigSaveI8, \
.override = INI_ConfigOverrideI8, \
}

INI_CONFIG_DECLARE_TYPE(I16);
#define INI_CONFIG_TYPE_I16 (INI_ConfigType) { \
.size = sizeof(int16_t), \
.load = INI_ConfigLoadI16, \
.save = INI_ConfigSaveI16, \
.override = INI_ConfigOverrideI16, \
}

INI_CONFIG_DECLARE_TYPE(I32);
#define INI_CONFIG_TYPE_I32 (INI_ConfigType) { \
.size = sizeof(int32_t), \
.load = INI_ConfigLoadI32, \
.save = INI_ConfigSaveI32, \
.override = INI_ConfigOverrideI32, \
}

#endif

+ 99
- 0
types/string.c 파일 보기

@@ -0,0 +1,99 @@
#include "string.h"


typedef bool INI_ConfigLoadParamsStringValidator(const char*);

void INI_ConfigEnsureValidString(INI_ConfigItem* item, const char* buffer) {
if (item->validator) {
INI_ConfigLoadParamsStringValidator* validator = item->validator;
if (validator(buffer)) {
memcpy(item->dest, buffer, item->type.size);
return;
}
memcpy(item->dest, item->default_value, item->type.size);
return;
}
memcpy(item->dest, buffer, item->type.size);
}

void INI_ConfigLoadString(INI_ConfigItem* item, const char* config_path) {
char buffer[item->type.size];
ini_gets(item->section, item->key, item->default_value, buffer, (int32_t) item->type.size, config_path);
INI_ConfigEnsureValidString(item, buffer);
}

INI_ConfigSaveItemResult INI_ConfigSaveString(INI_ConfigItem* item, const char* config_path) {
const char* dest = (const char*) item->dest;
if (item->validator) {
INI_ConfigLoadParamsStringValidator* validator = item->validator;
if (!validator(dest)) {
dest = (const char*) item->default_value;
}
}

if (!ini_puts(item->section, item->key, dest, config_path)) {
return INI_CONFIG_SAVE_ITEM_ERROR;
}

return INI_CONFIG_SAVE_ITEM_OK;
}

void INI_ConfigOverrideString(INI_ConfigItem* item, uint8_t argc, const char* argv[]) {
if (!item->cmdline_option) {
return;
}
const char* cmdline_buffer;
if ((cmdline_buffer = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) {
INI_ConfigEnsureValidString(item, cmdline_buffer);
}
}

void INI_ConfigLoad(INI_ConfigItem item[], const char* config_path) {
uint8_t i;
for (i = 0; item[i].type.size > 0; i += 1) {
if (!item[i].type.load) {
continue;
}
item[i].type.load(&item[i], config_path);
}
}

INI_ConfigSaveResult INI_ConfigSave(INI_ConfigItem item[], const char* config_path) {
uint8_t i;
int32_t problems = 0;
for (i = 0; item[i].type.size > 0; i += 1) {
int32_t result = item[i].type.save ? item[i].type.save(&item[i], config_path) : 0;

if (result < 0) {
problems |= (1 << (int32_t) i);
}
}

return -problems;
}

void INI_ConfigOverride(INI_ConfigItem item[], uint8_t argc, const char* argv[]) {
uint8_t i;
for (i = 0; item[i].type.size > 0; i += 1) {
if (!item[i].type.override) {
continue;
}

item[i].type.override(&item[i], argc, argv);
// TODO specify command-line arg external from config item?

}
}

INI_ConfigInitializeResult INI_ConfigInitialize(INI_ConfigItem item[], const char* config_path, uint8_t argc, const char* argv[]) {
INI_ConfigLoad(item, config_path);
INI_ConfigSaveResult save_result = INI_ConfigSave(item, config_path);
if (save_result < 0) {
return INI_CONFIG_INITIALIZE_RESULT_ERROR;
}
INI_ConfigOverride(item, argc, argv);
if (save_result > 0) {
return INI_CONFIG_INITIALIZE_RESULT_WARNING;
}
return INI_CONFIG_INITIALIZE_RESULT_OK;
}

+ 14
- 0
types/string.h 파일 보기

@@ -0,0 +1,14 @@
#ifndef INI_CONFIG_TYPES_STRING_H
#define INI_CONFIG_TYPES_STRING_H

#include "ini-config.h"

INI_CONFIG_DECLARE_TYPE(String);
#define INI_CONFIG_TYPE_STRING(X) (INI_ConfigType) { \
.size = (sizeof(char) * X), \
.load = INI_ConfigLoadString, \
.save = INI_ConfigSaveString, \
.override = INI_ConfigOverrideString, \
}

#endif

불러오는 중...
취소
저장