X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=source%2Fmupen64plus-core%2Fsrc%2Fapi%2Fconfig.c;fp=source%2Fmupen64plus-core%2Fsrc%2Fapi%2Fconfig.c;h=8095a89d7823c7c5f9048afa77f99efb792293de;hb=451ab91e3827a6384981b3300e2a7000d2eaba58;hp=0000000000000000000000000000000000000000;hpb=a2ab25365b5b0dddbee476d695d8a31151407581;p=mupen64plus-pandora.git diff --git a/source/mupen64plus-core/src/api/config.c b/source/mupen64plus-core/src/api/config.c new file mode 100644 index 0000000..8095a89 --- /dev/null +++ b/source/mupen64plus-core/src/api/config.c @@ -0,0 +1,1433 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus-core - api/config.c * + * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * + * Copyright (C) 2009 Richard Goedeken * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* This file contains the Core config functions which will be exported + * outside of the core library. + */ + +#include +#include +#include + +#define M64P_CORE_PROTOTYPES 1 +#include "m64p_types.h" +#include "m64p_config.h" +#include "config.h" +#include "callbacks.h" + +#include "main/util.h" + +#include "osal/files.h" +#include "osal/preproc.h" + +/* local types */ +#define MUPEN64PLUS_CFG_NAME "mupen64plus.cfg" + +#define SECTION_MAGIC 0xDBDC0580 + +typedef struct _config_var { + char *name; + m64p_type type; + union { + int integer; + float number; + char *string; + } val; + char *comment; + struct _config_var *next; + } config_var; + +typedef struct _config_section { + int magic; + char *name; + struct _config_var *first_var; + struct _config_section *next; + } config_section; + +typedef config_section *config_list; + +/* local variables */ +static int l_ConfigInit = 0; +static int l_SaveConfigOnExit = 0; +static char *l_DataDirOverride = NULL; +static char *l_ConfigDirOverride = NULL; +static config_list l_ConfigListActive = NULL; +static config_list l_ConfigListSaved = NULL; + +/* --------------- */ +/* local functions */ +/* --------------- */ + +static int is_numeric(const char *string) +{ + char chTemp[16]; + float fTemp; + int rval = sscanf(string, "%f%8s", &fTemp, chTemp); + + /* I want to find exactly one matched input item: a number with no garbage on the end */ + /* I use sscanf() instead of a custom loop because this routine must handle locales in which the decimal separator is not '.' */ + return (rval == 1); +} + +/* This function returns a pointer to the pointer of the requested section + * (i.e. a pointer the next field of the previous element, or to the first node). + * + * If there's no section named 'ParamName', returns the pointer to the next + * field of the last element in the list (such that derefencing it is NULL). + * + * Useful for operations that need to modify the links, e.g. deleting a section. + */ +static config_section **find_section_link(config_list *list, const char *ParamName) +{ + config_section **curr_sec_link; + for (curr_sec_link = list; *curr_sec_link != NULL; curr_sec_link = &(*curr_sec_link)->next) + { + if (osal_insensitive_strcmp(ParamName, (*curr_sec_link)->name) == 0) + break; + } + + return curr_sec_link; +} + +/* This function is similar to the previous function, but instead it returns a + * pointer to the pointer to the next section whose name is alphabetically + * greater than or equal to 'ParamName'. + * + * Useful for inserting a section in its alphabetical position. + */ +static config_section **find_alpha_section_link(config_list *list, const char *ParamName) +{ + config_section **curr_sec_link; + for (curr_sec_link = list; *curr_sec_link != NULL; curr_sec_link = &(*curr_sec_link)->next) + { + if (osal_insensitive_strcmp((*curr_sec_link)->name, ParamName) >= 0) + break; + } + + return curr_sec_link; +} + +static config_section *find_section(config_list list, const char *ParamName) +{ + return *find_section_link(&list, ParamName); +} + +static config_var *config_var_create(const char *ParamName, const char *ParamHelp) +{ + config_var *var = (config_var *) malloc(sizeof(config_var)); + + if (var == NULL || ParamName == NULL) + return NULL; + + memset(var, 0, sizeof(config_var)); + + var->name = strdup(ParamName); + if (var->name == NULL) + { + free(var); + return NULL; + } + + var->type = M64TYPE_INT; + var->val.integer = 0; + + if (ParamHelp != NULL) + { + var->comment = strdup(ParamHelp); + if (var->comment == NULL) + { + free(var->name); + free(var); + return NULL; + } + } + else + var->comment = NULL; + + var->next = NULL; + return var; +} + +static config_var *find_section_var(config_section *section, const char *ParamName) +{ + /* walk through the linked list of variables in the section */ + config_var *curr_var; + for (curr_var = section->first_var; curr_var != NULL; curr_var = curr_var->next) + { + if (osal_insensitive_strcmp(ParamName, curr_var->name) == 0) + return curr_var; + } + + /* couldn't find this configuration parameter */ + return NULL; +} + +static void append_var_to_section(config_section *section, config_var *var) +{ + config_var *last_var; + + if (section == NULL || var == NULL || section->magic != SECTION_MAGIC) + return; + + if (section->first_var == NULL) + { + section->first_var = var; + return; + } + + last_var = section->first_var; + while (last_var->next != NULL) + last_var = last_var->next; + + last_var->next = var; +} + +static void delete_var(config_var *var) +{ + if (var->type == M64TYPE_STRING) + free(var->val.string); + free(var->name); + free(var->comment); + free(var); +} + +static void delete_section(config_section *pSection) +{ + config_var *curr_var; + + if (pSection == NULL) + return; + + curr_var = pSection->first_var; + while (curr_var != NULL) + { + config_var *next_var = curr_var->next; + delete_var(curr_var); + curr_var = next_var; + } + + free(pSection->name); + free(pSection); +} + +static void delete_list(config_list *pConfigList) +{ + config_section *curr_section = *pConfigList; + while (curr_section != NULL) + { + config_section *next_section = curr_section->next; + /* delete the section itself */ + delete_section(curr_section); + + curr_section = next_section; + } + + *pConfigList = NULL; +} + +static config_section *config_section_create(const char *ParamName) +{ + config_section *sec; + + if (ParamName == NULL) + return NULL; + + sec = (config_section *) malloc(sizeof(config_section)); + if (sec == NULL) + return NULL; + + sec->magic = SECTION_MAGIC; + sec->name = strdup(ParamName); + if (sec->name == NULL) + { + free(sec); + return NULL; + } + sec->first_var = NULL; + sec->next = NULL; + return sec; +} + +static config_section * section_deepcopy(config_section *orig_section) +{ + config_section *new_section; + config_var *orig_var, *last_new_var; + + /* Input validation */ + if (orig_section == NULL) + return NULL; + + /* create and copy section struct */ + new_section = config_section_create(orig_section->name); + if (new_section == NULL) + return NULL; + + /* create and copy all section variables */ + orig_var = orig_section->first_var; + last_new_var = NULL; + while (orig_var != NULL) + { + config_var *new_var = config_var_create(orig_var->name, orig_var->comment); + if (new_var == NULL) + { + delete_section(new_section); + return NULL; + } + + new_var->type = orig_var->type; + switch (orig_var->type) + { + case M64TYPE_INT: + case M64TYPE_BOOL: + new_var->val.integer = orig_var->val.integer; + break; + + case M64TYPE_FLOAT: + new_var->val.number = orig_var->val.number; + break; + + case M64TYPE_STRING: + if (orig_var->val.string != NULL) + { + new_var->val.string = strdup(orig_var->val.string); + if (new_var->val.string == NULL) + { + delete_section(new_section); + return NULL; + } + } + else + new_var->val.string = NULL; + + break; + } + + /* add the new variable to the new section */ + if (last_new_var == NULL) + new_section->first_var = new_var; + else + last_new_var->next = new_var; + last_new_var = new_var; + /* advance variable pointer in original section variable list */ + orig_var = orig_var->next; + } + + return new_section; +} + +static void copy_configlist_active_to_saved(void) +{ + config_section *curr_section = l_ConfigListActive; + config_section *last_section = NULL; + + /* delete any pre-existing Saved config list */ + delete_list(&l_ConfigListSaved); + + /* duplicate all of the config sections in the Active list, adding them to the Saved list */ + while (curr_section != NULL) + { + config_section *new_section = section_deepcopy(curr_section); + if (new_section == NULL) break; + if (last_section == NULL) + l_ConfigListSaved = new_section; + else + last_section->next = new_section; + last_section = new_section; + curr_section = curr_section->next; + } +} + +static m64p_error write_configlist_file(void) +{ + config_section *curr_section; + const char *configpath; + char *filepath; + FILE *fPtr; + + /* get the full pathname to the config file and try to open it */ + configpath = ConfigGetUserConfigPath(); + if (configpath == NULL) + return M64ERR_FILES; + + filepath = combinepath(configpath, MUPEN64PLUS_CFG_NAME); + if (filepath == NULL) + return M64ERR_NO_MEMORY; + + fPtr = fopen(filepath, "wb"); + if (fPtr == NULL) + { + DebugMessage(M64MSG_ERROR, "Couldn't open configuration file '%s' for writing.", filepath); + free(filepath); + return M64ERR_FILES; + } + free(filepath); + + /* write out header */ + fprintf(fPtr, "# Mupen64Plus Configuration File\n"); + fprintf(fPtr, "# This file is automatically read and written by the Mupen64Plus Core library\n"); + + /* write out all of the config parameters from the Saved list */ + curr_section = l_ConfigListSaved; + while (curr_section != NULL) + { + config_var *curr_var = curr_section->first_var; + fprintf(fPtr, "\n[%s]\n\n", curr_section->name); + while (curr_var != NULL) + { + if (curr_var->comment != NULL && strlen(curr_var->comment) > 0) + fprintf(fPtr, "# %s\n", curr_var->comment); + if (curr_var->type == M64TYPE_INT) + fprintf(fPtr, "%s = %i\n", curr_var->name, curr_var->val.integer); + else if (curr_var->type == M64TYPE_FLOAT) + fprintf(fPtr, "%s = %f\n", curr_var->name, curr_var->val.number); + else if (curr_var->type == M64TYPE_BOOL && curr_var->val.integer) + fprintf(fPtr, "%s = True\n", curr_var->name); + else if (curr_var->type == M64TYPE_BOOL && !curr_var->val.integer) + fprintf(fPtr, "%s = False\n", curr_var->name); + else if (curr_var->type == M64TYPE_STRING && curr_var->val.string != NULL) + fprintf(fPtr, "%s = \"%s\"\n", curr_var->name, curr_var->val.string); + curr_var = curr_var->next; + } + fprintf(fPtr, "\n"); + curr_section = curr_section->next; + } + + fclose(fPtr); + return M64ERR_SUCCESS; +} + +/* ----------------------------------------------------------- */ +/* these functions are only to be used within the Core library */ +/* ----------------------------------------------------------- */ + +m64p_error ConfigInit(const char *ConfigDirOverride, const char *DataDirOverride) +{ + m64p_error rval; + const char *configpath = NULL; + char *filepath; + long filelen; + FILE *fPtr; + char *configtext; + + config_section *current_section = NULL; + char *line, *end, *lastcomment; + + if (l_ConfigInit) + return M64ERR_ALREADY_INIT; + l_ConfigInit = 1; + + /* if a data directory was specified, make a copy of it */ + if (DataDirOverride != NULL) + { + l_DataDirOverride = strdup(DataDirOverride); + if (l_DataDirOverride == NULL) + return M64ERR_NO_MEMORY; + } + + /* if a config directory was specified, make a copy of it */ + if (ConfigDirOverride != NULL) + { + l_ConfigDirOverride = strdup(ConfigDirOverride); + if (l_ConfigDirOverride == NULL) + return M64ERR_NO_MEMORY; + } + + /* get the full pathname to the config file and try to open it */ + configpath = ConfigGetUserConfigPath(); + if (configpath == NULL) + return M64ERR_FILES; + + filepath = combinepath(configpath, MUPEN64PLUS_CFG_NAME); + if (filepath == NULL) + return M64ERR_NO_MEMORY; + + fPtr = fopen(filepath, "rb"); + if (fPtr == NULL) + { + DebugMessage(M64MSG_INFO, "Couldn't open configuration file '%s'. Using defaults.", filepath); + free(filepath); + l_SaveConfigOnExit = 1; /* auto-save the config file so that the defaults will be saved to disk */ + return M64ERR_SUCCESS; + } + free(filepath); + + /* read the entire config file */ + fseek(fPtr, 0L, SEEK_END); + filelen = ftell(fPtr); + fseek(fPtr, 0L, SEEK_SET); + + configtext = (char *) malloc(filelen + 1); + if (configtext == NULL) + { + fclose(fPtr); + return M64ERR_NO_MEMORY; + } + if (fread(configtext, 1, filelen, fPtr) != filelen) + { + free(configtext); + fclose(fPtr); + return M64ERR_FILES; + } + fclose(fPtr); + + /* parse the file data */ + current_section = NULL; + line = configtext; + end = configtext + filelen; + lastcomment = NULL; + *end = 0; + while (line < end) + { + ini_line l = ini_parse_line(&line); + switch (l.type) + { + case INI_COMMENT: + lastcomment = l.value; + break; + + case INI_SECTION: + rval = ConfigOpenSection(l.name, (m64p_handle *) ¤t_section); + if (rval != M64ERR_SUCCESS) + { + free(configtext); + return rval; + } + lastcomment = NULL; + break; + + case INI_PROPERTY: + if (l.value[0] == '"' && l.value[strlen(l.value)-1] == '"') + { + l.value++; + l.value[strlen(l.value)-1] = 0; + ConfigSetDefaultString((m64p_handle) current_section, l.name, l.value, lastcomment); + } + else if (osal_insensitive_strcmp(l.value, "false") == 0) + { + ConfigSetDefaultBool((m64p_handle) current_section, l.name, 0, lastcomment); + } + else if (osal_insensitive_strcmp(l.value, "true") == 0) + { + ConfigSetDefaultBool((m64p_handle) current_section, l.name, 1, lastcomment); + } + else if (is_numeric(l.value)) + { + int val_int = (int) strtol(l.value, NULL, 10); + float val_float = (float) strtod(l.value, NULL); + if ((val_float - val_int) != 0.0) + ConfigSetDefaultFloat((m64p_handle) current_section, l.name, val_float, lastcomment); + else + ConfigSetDefaultInt((m64p_handle) current_section, l.name, val_int, lastcomment); + } + else + { + /* assume that it's a string */ + ConfigSetDefaultString((m64p_handle) current_section, l.name, l.value, lastcomment); + } + lastcomment = NULL; + break; + + default: + break; + } + } + + /* release memory used for config file text */ + free(configtext); + + /* duplicate the entire config data list, to store a copy of the list which represents the state of the file on disk */ + copy_configlist_active_to_saved(); + + return M64ERR_SUCCESS; +} + +m64p_error ConfigShutdown(void) +{ + /* first, save the file if necessary */ + if (l_SaveConfigOnExit) + ConfigSaveFile(); + + /* reset the initialized flag */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + l_ConfigInit = 0; + + /* free any malloc'd local variables */ + if (l_DataDirOverride != NULL) + { + free(l_DataDirOverride); + l_DataDirOverride = NULL; + } + if (l_ConfigDirOverride != NULL) + { + free(l_ConfigDirOverride); + l_ConfigDirOverride = NULL; + } + + /* free all of the memory in the 2 lists */ + delete_list(&l_ConfigListActive); + delete_list(&l_ConfigListSaved); + + return M64ERR_SUCCESS; +} + +/* ------------------------------------------------ */ +/* Selector functions, exported outside of the Core */ +/* ------------------------------------------------ */ + +EXPORT m64p_error CALL ConfigListSections(void *context, void (*SectionListCallback)(void * context, const char * SectionName)) +{ + config_section *curr_section; + + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (SectionListCallback == NULL) + return M64ERR_INPUT_ASSERT; + + /* just walk through the section list, making a callback for each section name */ + curr_section = l_ConfigListActive; + while (curr_section != NULL) + { + (*SectionListCallback)(context, curr_section->name); + curr_section = curr_section->next; + } + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigOpenSection(const char *SectionName, m64p_handle *ConfigSectionHandle) +{ + config_section **curr_section; + config_section *new_section; + + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (SectionName == NULL || ConfigSectionHandle == NULL) + return M64ERR_INPUT_ASSERT; + + /* walk through the section list, looking for a case-insensitive name match */ + curr_section = find_alpha_section_link(&l_ConfigListActive, SectionName); + if (*curr_section != NULL && osal_insensitive_strcmp(SectionName, (*curr_section)->name) == 0) + { + *ConfigSectionHandle = *curr_section; + return M64ERR_SUCCESS; + } + + /* didn't find the section, so create new one */ + new_section = config_section_create(SectionName); + if (new_section == NULL) + return M64ERR_NO_MEMORY; + + /* add section to list in alphabetical order */ + new_section->next = *curr_section; + *curr_section = new_section; + + *ConfigSectionHandle = new_section; + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigListParameters(m64p_handle ConfigSectionHandle, void *context, void (*ParameterListCallback)(void * context, const char *ParamName, m64p_type ParamType)) +{ + config_section *section; + config_var *curr_var; + + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParameterListCallback == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* walk through this section's parameter list, making a callback for each parameter */ + curr_var = section->first_var; + while (curr_var != NULL) + { + (*ParameterListCallback)(context, curr_var->name, curr_var->type); + curr_var = curr_var->next; + } + + return M64ERR_SUCCESS; +} + +EXPORT int CALL ConfigHasUnsavedChanges(const char *SectionName) +{ + config_section *input_section, *curr_section; + config_var *active_var, *saved_var; + + /* check input conditions */ + if (!l_ConfigInit) + { + DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Core config not initialized!"); + return 0; + } + + /* if SectionName is NULL or blank, then check all sections */ + if (SectionName == NULL || strlen(SectionName) < 1) + { + int iNumActiveSections = 0, iNumSavedSections = 0; + /* first, search through all sections in Active list. Recursively call ourself and return 1 if changed */ + curr_section = l_ConfigListActive; + while (curr_section != NULL) + { + if (ConfigHasUnsavedChanges(curr_section->name)) + return 1; + curr_section = curr_section->next; + iNumActiveSections++; + } + /* Next, count the number of Saved sections and see if the count matches */ + curr_section = l_ConfigListSaved; + while (curr_section != NULL) + { + curr_section = curr_section->next; + iNumSavedSections++; + } + if (iNumActiveSections == iNumSavedSections) + return 0; /* no changes */ + else + return 1; + } + + /* walk through the Active section list, looking for a case-insensitive name match with input string */ + input_section = find_section(l_ConfigListActive, SectionName); + if (input_section == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): section name '%s' not found!", SectionName); + return 0; + } + + /* walk through the Saved section list, looking for a case-insensitive name match */ + curr_section = find_section(l_ConfigListSaved, SectionName); + if (curr_section == NULL) + { + /* if this section isn't present in saved list, then it has been newly created */ + return 1; + } + + /* compare all of the variables in the two sections. They are expected to be in the same order */ + active_var = input_section->first_var; + saved_var = curr_section->first_var; + while (active_var != NULL && saved_var != NULL) + { + if (strcmp(active_var->name, saved_var->name) != 0) + return 1; + if (active_var->type != saved_var->type) + return 1; + switch(active_var->type) + { + case M64TYPE_INT: + if (active_var->val.integer != saved_var->val.integer) + return 1; + break; + case M64TYPE_FLOAT: + if (active_var->val.number != saved_var->val.number) + return 1; + break; + case M64TYPE_BOOL: + if ((active_var->val.integer != 0) != (saved_var->val.integer != 0)) + return 1; + break; + case M64TYPE_STRING: + if (active_var->val.string == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Variable '%s' NULL Active string pointer!", active_var->name); + return 1; + } + if (saved_var->val.string == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Variable '%s' NULL Saved string pointer!", active_var->name); + return 1; + } + if (strcmp(active_var->val.string, saved_var->val.string) != 0) + return 1; + break; + default: + DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Invalid variable '%s' type %i!", active_var->name, active_var->type); + return 1; + } + if (active_var->comment != NULL && saved_var->comment != NULL && strcmp(active_var->comment, saved_var->comment) != 0) + return 1; + active_var = active_var->next; + saved_var = saved_var->next; + } + + /* any extra new variables on the end, or deleted variables? */ + if (active_var != NULL || saved_var != NULL) + return 1; + + /* exactly the same */ + return 0; +} + +/* ------------------------------------------------------- */ +/* Modifier functions, exported outside of the Core */ +/* ------------------------------------------------------- */ + +EXPORT m64p_error CALL ConfigDeleteSection(const char *SectionName) +{ + config_section **curr_section_link; + config_section *next_section; + + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (l_ConfigListActive == NULL) + return M64ERR_INPUT_NOT_FOUND; + + /* find the named section and pull it out of the list */ + curr_section_link = find_section_link(&l_ConfigListActive, SectionName); + if (*curr_section_link == NULL) + return M64ERR_INPUT_NOT_FOUND; + + next_section = (*curr_section_link)->next; + + /* delete the named section */ + delete_section(*curr_section_link); + + /* fix the pointer to point to the next section after the deleted one */ + *curr_section_link = next_section; + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigSaveFile(void) +{ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + + /* copy the active config list to the saved config list */ + copy_configlist_active_to_saved(); + + /* write the saved config list out to a file */ + return (write_configlist_file()); +} + +EXPORT m64p_error CALL ConfigSaveSection(const char *SectionName) +{ + config_section *curr_section, *new_section; + config_section **insertion_point; + + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (SectionName == NULL || strlen(SectionName) < 1) + return M64ERR_INPUT_ASSERT; + + /* walk through the Active section list, looking for a case-insensitive name match */ + curr_section = find_section(l_ConfigListActive, SectionName); + if (curr_section == NULL) + return M64ERR_INPUT_NOT_FOUND; + + /* duplicate this section */ + new_section = section_deepcopy(curr_section); + if (new_section == NULL) + return M64ERR_NO_MEMORY; + + /* update config section that's in the Saved list with the new one */ + insertion_point = find_alpha_section_link(&l_ConfigListSaved, SectionName); + if (*insertion_point != NULL && osal_insensitive_strcmp((*insertion_point)->name, SectionName) == 0) + { + /* the section exists in the saved list and will be replaced */ + new_section->next = (*insertion_point)->next; + delete_section(*insertion_point); + *insertion_point = new_section; + } + else + { + /* the section didn't exist in the saved list and has to be inserted */ + new_section->next = *insertion_point; + *insertion_point = new_section; + } + + /* write the saved config list out to a file */ + return (write_configlist_file()); +} + +EXPORT m64p_error CALL ConfigRevertChanges(const char *SectionName) +{ + config_section **active_section_link, *active_section, *saved_section, *new_section; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (SectionName == NULL) + return M64ERR_INPUT_ASSERT; + + /* walk through the Active section list, looking for a case-insensitive name match with input string */ + active_section_link = find_section_link(&l_ConfigListActive, SectionName); + active_section = *active_section_link; + if (active_section == NULL) + return M64ERR_INPUT_NOT_FOUND; + + /* walk through the Saved section list, looking for a case-insensitive name match */ + saved_section = find_section(l_ConfigListSaved, SectionName); + if (saved_section == NULL) + { + /* if this section isn't present in saved list, then it has been newly created */ + return M64ERR_INPUT_NOT_FOUND; + } + + /* copy the section as it is on the disk */ + new_section = section_deepcopy(saved_section); + if (new_section == NULL) + return M64ERR_NO_MEMORY; + + /* replace active_section with saved_section in the linked list */ + *active_section_link = new_section; + new_section->next = active_section->next; + + /* release memory associated with active_section */ + delete_section(active_section); + + return M64ERR_SUCCESS; +} + + +/* ------------------------------------------------------- */ +/* Generic Get/Set functions, exported outside of the Core */ +/* ------------------------------------------------------- */ + +EXPORT m64p_error CALL ConfigSetParameter(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type ParamType, const void *ParamValue) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL || (int) ParamType < 1 || (int) ParamType > 4) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter doesn't already exist, then create it and add it to the section */ + var = find_section_var(section, ParamName); + if (var == NULL) + { + var = config_var_create(ParamName, NULL); + if (var == NULL) + return M64ERR_NO_MEMORY; + append_var_to_section(section, var); + } + + /* cleanup old values */ + switch (var->type) + { + case M64TYPE_STRING: + free(var->val.string); + break; + default: + break; + } + + /* set this parameter's value */ + var->type = ParamType; + switch(ParamType) + { + case M64TYPE_INT: + var->val.integer = *((int *) ParamValue); + break; + case M64TYPE_FLOAT: + var->val.number = *((float *) ParamValue); + break; + case M64TYPE_BOOL: + var->val.integer = (*((int *) ParamValue) != 0); + break; + case M64TYPE_STRING: + var->val.string = strdup((char *)ParamValue); + if (var->val.string == NULL) + return M64ERR_NO_MEMORY; + break; + default: + /* this is logically impossible because of the ParamType check at the top of this function */ + break; + } + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigGetParameter(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type ParamType, void *ParamValue, int MaxSize) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL || (int) ParamType < 1 || (int) ParamType > 4) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + return M64ERR_INPUT_NOT_FOUND; + + /* call the specific Get function to translate the parameter to the desired type */ + switch(ParamType) + { + case M64TYPE_INT: + if (MaxSize < sizeof(int)) return M64ERR_INPUT_INVALID; + if (var->type != M64TYPE_INT && var->type != M64TYPE_FLOAT) return M64ERR_WRONG_TYPE; + *((int *) ParamValue) = ConfigGetParamInt(ConfigSectionHandle, ParamName); + break; + case M64TYPE_FLOAT: + if (MaxSize < sizeof(float)) return M64ERR_INPUT_INVALID; + if (var->type != M64TYPE_INT && var->type != M64TYPE_FLOAT) return M64ERR_WRONG_TYPE; + *((float *) ParamValue) = ConfigGetParamFloat(ConfigSectionHandle, ParamName); + break; + case M64TYPE_BOOL: + if (MaxSize < sizeof(int)) return M64ERR_INPUT_INVALID; + if (var->type != M64TYPE_BOOL && var->type != M64TYPE_INT) return M64ERR_WRONG_TYPE; + *((int *) ParamValue) = ConfigGetParamBool(ConfigSectionHandle, ParamName); + break; + case M64TYPE_STRING: + { + const char *string; + if (MaxSize < 1) return M64ERR_INPUT_INVALID; + if (var->type != M64TYPE_STRING && var->type != M64TYPE_BOOL) return M64ERR_WRONG_TYPE; + string = ConfigGetParamString(ConfigSectionHandle, ParamName); + strncpy((char *) ParamValue, string, MaxSize); + *((char *) ParamValue + MaxSize - 1) = 0; + break; + } + default: + /* this is logically impossible because of the ParamType check at the top of this function */ + break; + } + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigGetParameterType(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type *ParamType) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL || ParamType == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + return M64ERR_INPUT_NOT_FOUND; + + *ParamType = var->type; + return M64ERR_SUCCESS; +} + + +EXPORT const char * CALL ConfigGetParameterHelp(m64p_handle ConfigSectionHandle, const char *ParamName) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL) + return NULL; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return NULL; + + /* if this parameter doesn't exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + return NULL; + + return var->comment; +} + +/* ------------------------------------------------------- */ +/* Special Get/Set functions, exported outside of the Core */ +/* ------------------------------------------------------- */ + +EXPORT m64p_error CALL ConfigSetDefaultInt(m64p_handle ConfigSectionHandle, const char *ParamName, int ParamValue, const char *ParamHelp) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter already exists, then just return successfully */ + var = find_section_var(section, ParamName); + if (var != NULL) + return M64ERR_SUCCESS; + + /* otherwise create a new config_var object and add it to this section */ + var = config_var_create(ParamName, ParamHelp); + if (var == NULL) + return M64ERR_NO_MEMORY; + var->type = M64TYPE_INT; + var->val.integer = ParamValue; + append_var_to_section(section, var); + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigSetDefaultFloat(m64p_handle ConfigSectionHandle, const char *ParamName, float ParamValue, const char *ParamHelp) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter already exists, then just return successfully */ + var = find_section_var(section, ParamName); + if (var != NULL) + return M64ERR_SUCCESS; + + /* otherwise create a new config_var object and add it to this section */ + var = config_var_create(ParamName, ParamHelp); + if (var == NULL) + return M64ERR_NO_MEMORY; + var->type = M64TYPE_FLOAT; + var->val.number = ParamValue; + append_var_to_section(section, var); + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigSetDefaultBool(m64p_handle ConfigSectionHandle, const char *ParamName, int ParamValue, const char *ParamHelp) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter already exists, then just return successfully */ + var = find_section_var(section, ParamName); + if (var != NULL) + return M64ERR_SUCCESS; + + /* otherwise create a new config_var object and add it to this section */ + var = config_var_create(ParamName, ParamHelp); + if (var == NULL) + return M64ERR_NO_MEMORY; + var->type = M64TYPE_BOOL; + var->val.integer = ParamValue ? 1 : 0; + append_var_to_section(section, var); + + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL ConfigSetDefaultString(m64p_handle ConfigSectionHandle, const char *ParamName, const char * ParamValue, const char *ParamHelp) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit) + return M64ERR_NOT_INIT; + if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL) + return M64ERR_INPUT_ASSERT; + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + return M64ERR_INPUT_INVALID; + + /* if this parameter already exists, then just return successfully */ + var = find_section_var(section, ParamName); + if (var != NULL) + return M64ERR_SUCCESS; + + /* otherwise create a new config_var object and add it to this section */ + var = config_var_create(ParamName, ParamHelp); + if (var == NULL) + return M64ERR_NO_MEMORY; + var->type = M64TYPE_STRING; + var->val.string = strdup(ParamValue); + if (var->val.string == NULL) + { + delete_var(var); + return M64ERR_NO_MEMORY; + } + append_var_to_section(section, var); + + return M64ERR_SUCCESS; +} + +EXPORT int CALL ConfigGetParamInt(m64p_handle ConfigSectionHandle, const char *ParamName) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): Input assertion!"); + return 0; + } + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): ConfigSectionHandle invalid!"); + return 0; + } + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): Parameter '%s' not found!", ParamName); + return 0; + } + + /* translate the actual variable type to an int */ + switch(var->type) + { + case M64TYPE_INT: + return var->val.integer; + case M64TYPE_FLOAT: + return (int) var->val.number; + case M64TYPE_BOOL: + return (var->val.integer != 0); + case M64TYPE_STRING: + return atoi(var->val.string); + default: + DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): invalid internal parameter type for '%s'", ParamName); + return 0; + } + + return 0; +} + +EXPORT float CALL ConfigGetParamFloat(m64p_handle ConfigSectionHandle, const char *ParamName) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): Input assertion!"); + return 0.0; + } + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): ConfigSectionHandle invalid!"); + return 0.0; + } + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): Parameter '%s' not found!", ParamName); + return 0.0; + } + + /* translate the actual variable type to an int */ + switch(var->type) + { + case M64TYPE_INT: + return (float) var->val.integer; + case M64TYPE_FLOAT: + return var->val.number; + case M64TYPE_BOOL: + return (var->val.integer != 0) ? 1.0f : 0.0f; + case M64TYPE_STRING: + return (float) atof(var->val.string); + default: + DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): invalid internal parameter type for '%s'", ParamName); + return 0.0; + } + + return 0.0; +} + +EXPORT int CALL ConfigGetParamBool(m64p_handle ConfigSectionHandle, const char *ParamName) +{ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): Input assertion!"); + return 0; + } + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): ConfigSectionHandle invalid!"); + return 0; + } + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): Parameter '%s' not found!", ParamName); + return 0; + } + + /* translate the actual variable type to an int */ + switch(var->type) + { + case M64TYPE_INT: + return (var->val.integer != 0); + case M64TYPE_FLOAT: + return (var->val.number != 0.0); + case M64TYPE_BOOL: + return var->val.integer; + case M64TYPE_STRING: + return (osal_insensitive_strcmp(var->val.string, "true") == 0); + default: + DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): invalid internal parameter type for '%s'", ParamName); + return 0; + } + + return 0; +} + +EXPORT const char * CALL ConfigGetParamString(m64p_handle ConfigSectionHandle, const char *ParamName) +{ + static char outstr[64]; /* warning: not thread safe */ + config_section *section; + config_var *var; + + /* check input conditions */ + if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): Input assertion!"); + return ""; + } + + section = (config_section *) ConfigSectionHandle; + if (section->magic != SECTION_MAGIC) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): ConfigSectionHandle invalid!"); + return ""; + } + + /* if this parameter doesn't already exist, return an error */ + var = find_section_var(section, ParamName); + if (var == NULL) + { + DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): Parameter '%s' not found!", ParamName); + return ""; + } + + /* translate the actual variable type to an int */ + switch(var->type) + { + case M64TYPE_INT: + snprintf(outstr, 63, "%i", var->val.integer); + outstr[63] = 0; + return outstr; + case M64TYPE_FLOAT: + snprintf(outstr, 63, "%f", var->val.number); + outstr[63] = 0; + return outstr; + case M64TYPE_BOOL: + return (var->val.integer ? "True" : "False"); + case M64TYPE_STRING: + return var->val.string; + default: + DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): invalid internal parameter type for '%s'", ParamName); + return ""; + } + + return ""; +} + +/* ------------------------------------------------------ */ +/* OS Abstraction functions, exported outside of the Core */ +/* ------------------------------------------------------ */ + +EXPORT const char * CALL ConfigGetSharedDataFilepath(const char *filename) +{ + const char *configsharepath = NULL; + m64p_handle CoreHandle = NULL; + + /* check input parameter */ + if (filename == NULL) return NULL; + + /* try to get the SharedDataPath string variable in the Core configuration section */ + if (ConfigOpenSection("Core", &CoreHandle) == M64ERR_SUCCESS) + { + configsharepath = ConfigGetParamString(CoreHandle, "SharedDataPath"); + } + + return osal_get_shared_filepath(filename, l_DataDirOverride, configsharepath); +} + +EXPORT const char * CALL ConfigGetUserConfigPath(void) +{ + if (l_ConfigDirOverride != NULL) + { + osal_mkdirp(l_ConfigDirOverride, 0700); + return l_ConfigDirOverride; + } + else + return osal_get_user_configpath(); +} + +EXPORT const char * CALL ConfigGetUserDataPath(void) +{ + return osal_get_user_datapath(); +} + +EXPORT const char * CALL ConfigGetUserCachePath(void) +{ + return osal_get_user_cachepath(); +} +