Core commit. Compile and run on the OpenPandora
[mupen64plus-pandora.git] / source / mupen64plus-core / src / api / config.c
CommitLineData
451ab91e 1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus-core - api/config.c *
3 * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
4 * Copyright (C) 2009 Richard Goedeken *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22/* This file contains the Core config functions which will be exported
23 * outside of the core library.
24 */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#define M64P_CORE_PROTOTYPES 1
31#include "m64p_types.h"
32#include "m64p_config.h"
33#include "config.h"
34#include "callbacks.h"
35
36#include "main/util.h"
37
38#include "osal/files.h"
39#include "osal/preproc.h"
40
41/* local types */
42#define MUPEN64PLUS_CFG_NAME "mupen64plus.cfg"
43
44#define SECTION_MAGIC 0xDBDC0580
45
46typedef struct _config_var {
47 char *name;
48 m64p_type type;
49 union {
50 int integer;
51 float number;
52 char *string;
53 } val;
54 char *comment;
55 struct _config_var *next;
56 } config_var;
57
58typedef struct _config_section {
59 int magic;
60 char *name;
61 struct _config_var *first_var;
62 struct _config_section *next;
63 } config_section;
64
65typedef config_section *config_list;
66
67/* local variables */
68static int l_ConfigInit = 0;
69static int l_SaveConfigOnExit = 0;
70static char *l_DataDirOverride = NULL;
71static char *l_ConfigDirOverride = NULL;
72static config_list l_ConfigListActive = NULL;
73static config_list l_ConfigListSaved = NULL;
74
75/* --------------- */
76/* local functions */
77/* --------------- */
78
79static int is_numeric(const char *string)
80{
81 char chTemp[16];
82 float fTemp;
83 int rval = sscanf(string, "%f%8s", &fTemp, chTemp);
84
85 /* I want to find exactly one matched input item: a number with no garbage on the end */
86 /* I use sscanf() instead of a custom loop because this routine must handle locales in which the decimal separator is not '.' */
87 return (rval == 1);
88}
89
90/* This function returns a pointer to the pointer of the requested section
91 * (i.e. a pointer the next field of the previous element, or to the first node).
92 *
93 * If there's no section named 'ParamName', returns the pointer to the next
94 * field of the last element in the list (such that derefencing it is NULL).
95 *
96 * Useful for operations that need to modify the links, e.g. deleting a section.
97 */
98static config_section **find_section_link(config_list *list, const char *ParamName)
99{
100 config_section **curr_sec_link;
101 for (curr_sec_link = list; *curr_sec_link != NULL; curr_sec_link = &(*curr_sec_link)->next)
102 {
103 if (osal_insensitive_strcmp(ParamName, (*curr_sec_link)->name) == 0)
104 break;
105 }
106
107 return curr_sec_link;
108}
109
110/* This function is similar to the previous function, but instead it returns a
111 * pointer to the pointer to the next section whose name is alphabetically
112 * greater than or equal to 'ParamName'.
113 *
114 * Useful for inserting a section in its alphabetical position.
115 */
116static config_section **find_alpha_section_link(config_list *list, const char *ParamName)
117{
118 config_section **curr_sec_link;
119 for (curr_sec_link = list; *curr_sec_link != NULL; curr_sec_link = &(*curr_sec_link)->next)
120 {
121 if (osal_insensitive_strcmp((*curr_sec_link)->name, ParamName) >= 0)
122 break;
123 }
124
125 return curr_sec_link;
126}
127
128static config_section *find_section(config_list list, const char *ParamName)
129{
130 return *find_section_link(&list, ParamName);
131}
132
133static config_var *config_var_create(const char *ParamName, const char *ParamHelp)
134{
135 config_var *var = (config_var *) malloc(sizeof(config_var));
136
137 if (var == NULL || ParamName == NULL)
138 return NULL;
139
140 memset(var, 0, sizeof(config_var));
141
142 var->name = strdup(ParamName);
143 if (var->name == NULL)
144 {
145 free(var);
146 return NULL;
147 }
148
149 var->type = M64TYPE_INT;
150 var->val.integer = 0;
151
152 if (ParamHelp != NULL)
153 {
154 var->comment = strdup(ParamHelp);
155 if (var->comment == NULL)
156 {
157 free(var->name);
158 free(var);
159 return NULL;
160 }
161 }
162 else
163 var->comment = NULL;
164
165 var->next = NULL;
166 return var;
167}
168
169static config_var *find_section_var(config_section *section, const char *ParamName)
170{
171 /* walk through the linked list of variables in the section */
172 config_var *curr_var;
173 for (curr_var = section->first_var; curr_var != NULL; curr_var = curr_var->next)
174 {
175 if (osal_insensitive_strcmp(ParamName, curr_var->name) == 0)
176 return curr_var;
177 }
178
179 /* couldn't find this configuration parameter */
180 return NULL;
181}
182
183static void append_var_to_section(config_section *section, config_var *var)
184{
185 config_var *last_var;
186
187 if (section == NULL || var == NULL || section->magic != SECTION_MAGIC)
188 return;
189
190 if (section->first_var == NULL)
191 {
192 section->first_var = var;
193 return;
194 }
195
196 last_var = section->first_var;
197 while (last_var->next != NULL)
198 last_var = last_var->next;
199
200 last_var->next = var;
201}
202
203static void delete_var(config_var *var)
204{
205 if (var->type == M64TYPE_STRING)
206 free(var->val.string);
207 free(var->name);
208 free(var->comment);
209 free(var);
210}
211
212static void delete_section(config_section *pSection)
213{
214 config_var *curr_var;
215
216 if (pSection == NULL)
217 return;
218
219 curr_var = pSection->first_var;
220 while (curr_var != NULL)
221 {
222 config_var *next_var = curr_var->next;
223 delete_var(curr_var);
224 curr_var = next_var;
225 }
226
227 free(pSection->name);
228 free(pSection);
229}
230
231static void delete_list(config_list *pConfigList)
232{
233 config_section *curr_section = *pConfigList;
234 while (curr_section != NULL)
235 {
236 config_section *next_section = curr_section->next;
237 /* delete the section itself */
238 delete_section(curr_section);
239
240 curr_section = next_section;
241 }
242
243 *pConfigList = NULL;
244}
245
246static config_section *config_section_create(const char *ParamName)
247{
248 config_section *sec;
249
250 if (ParamName == NULL)
251 return NULL;
252
253 sec = (config_section *) malloc(sizeof(config_section));
254 if (sec == NULL)
255 return NULL;
256
257 sec->magic = SECTION_MAGIC;
258 sec->name = strdup(ParamName);
259 if (sec->name == NULL)
260 {
261 free(sec);
262 return NULL;
263 }
264 sec->first_var = NULL;
265 sec->next = NULL;
266 return sec;
267}
268
269static config_section * section_deepcopy(config_section *orig_section)
270{
271 config_section *new_section;
272 config_var *orig_var, *last_new_var;
273
274 /* Input validation */
275 if (orig_section == NULL)
276 return NULL;
277
278 /* create and copy section struct */
279 new_section = config_section_create(orig_section->name);
280 if (new_section == NULL)
281 return NULL;
282
283 /* create and copy all section variables */
284 orig_var = orig_section->first_var;
285 last_new_var = NULL;
286 while (orig_var != NULL)
287 {
288 config_var *new_var = config_var_create(orig_var->name, orig_var->comment);
289 if (new_var == NULL)
290 {
291 delete_section(new_section);
292 return NULL;
293 }
294
295 new_var->type = orig_var->type;
296 switch (orig_var->type)
297 {
298 case M64TYPE_INT:
299 case M64TYPE_BOOL:
300 new_var->val.integer = orig_var->val.integer;
301 break;
302
303 case M64TYPE_FLOAT:
304 new_var->val.number = orig_var->val.number;
305 break;
306
307 case M64TYPE_STRING:
308 if (orig_var->val.string != NULL)
309 {
310 new_var->val.string = strdup(orig_var->val.string);
311 if (new_var->val.string == NULL)
312 {
313 delete_section(new_section);
314 return NULL;
315 }
316 }
317 else
318 new_var->val.string = NULL;
319
320 break;
321 }
322
323 /* add the new variable to the new section */
324 if (last_new_var == NULL)
325 new_section->first_var = new_var;
326 else
327 last_new_var->next = new_var;
328 last_new_var = new_var;
329 /* advance variable pointer in original section variable list */
330 orig_var = orig_var->next;
331 }
332
333 return new_section;
334}
335
336static void copy_configlist_active_to_saved(void)
337{
338 config_section *curr_section = l_ConfigListActive;
339 config_section *last_section = NULL;
340
341 /* delete any pre-existing Saved config list */
342 delete_list(&l_ConfigListSaved);
343
344 /* duplicate all of the config sections in the Active list, adding them to the Saved list */
345 while (curr_section != NULL)
346 {
347 config_section *new_section = section_deepcopy(curr_section);
348 if (new_section == NULL) break;
349 if (last_section == NULL)
350 l_ConfigListSaved = new_section;
351 else
352 last_section->next = new_section;
353 last_section = new_section;
354 curr_section = curr_section->next;
355 }
356}
357
358static m64p_error write_configlist_file(void)
359{
360 config_section *curr_section;
361 const char *configpath;
362 char *filepath;
363 FILE *fPtr;
364
365 /* get the full pathname to the config file and try to open it */
366 configpath = ConfigGetUserConfigPath();
367 if (configpath == NULL)
368 return M64ERR_FILES;
369
370 filepath = combinepath(configpath, MUPEN64PLUS_CFG_NAME);
371 if (filepath == NULL)
372 return M64ERR_NO_MEMORY;
373
374 fPtr = fopen(filepath, "wb");
375 if (fPtr == NULL)
376 {
377 DebugMessage(M64MSG_ERROR, "Couldn't open configuration file '%s' for writing.", filepath);
378 free(filepath);
379 return M64ERR_FILES;
380 }
381 free(filepath);
382
383 /* write out header */
384 fprintf(fPtr, "# Mupen64Plus Configuration File\n");
385 fprintf(fPtr, "# This file is automatically read and written by the Mupen64Plus Core library\n");
386
387 /* write out all of the config parameters from the Saved list */
388 curr_section = l_ConfigListSaved;
389 while (curr_section != NULL)
390 {
391 config_var *curr_var = curr_section->first_var;
392 fprintf(fPtr, "\n[%s]\n\n", curr_section->name);
393 while (curr_var != NULL)
394 {
395 if (curr_var->comment != NULL && strlen(curr_var->comment) > 0)
396 fprintf(fPtr, "# %s\n", curr_var->comment);
397 if (curr_var->type == M64TYPE_INT)
398 fprintf(fPtr, "%s = %i\n", curr_var->name, curr_var->val.integer);
399 else if (curr_var->type == M64TYPE_FLOAT)
400 fprintf(fPtr, "%s = %f\n", curr_var->name, curr_var->val.number);
401 else if (curr_var->type == M64TYPE_BOOL && curr_var->val.integer)
402 fprintf(fPtr, "%s = True\n", curr_var->name);
403 else if (curr_var->type == M64TYPE_BOOL && !curr_var->val.integer)
404 fprintf(fPtr, "%s = False\n", curr_var->name);
405 else if (curr_var->type == M64TYPE_STRING && curr_var->val.string != NULL)
406 fprintf(fPtr, "%s = \"%s\"\n", curr_var->name, curr_var->val.string);
407 curr_var = curr_var->next;
408 }
409 fprintf(fPtr, "\n");
410 curr_section = curr_section->next;
411 }
412
413 fclose(fPtr);
414 return M64ERR_SUCCESS;
415}
416
417/* ----------------------------------------------------------- */
418/* these functions are only to be used within the Core library */
419/* ----------------------------------------------------------- */
420
421m64p_error ConfigInit(const char *ConfigDirOverride, const char *DataDirOverride)
422{
423 m64p_error rval;
424 const char *configpath = NULL;
425 char *filepath;
426 long filelen;
427 FILE *fPtr;
428 char *configtext;
429
430 config_section *current_section = NULL;
431 char *line, *end, *lastcomment;
432
433 if (l_ConfigInit)
434 return M64ERR_ALREADY_INIT;
435 l_ConfigInit = 1;
436
437 /* if a data directory was specified, make a copy of it */
438 if (DataDirOverride != NULL)
439 {
440 l_DataDirOverride = strdup(DataDirOverride);
441 if (l_DataDirOverride == NULL)
442 return M64ERR_NO_MEMORY;
443 }
444
445 /* if a config directory was specified, make a copy of it */
446 if (ConfigDirOverride != NULL)
447 {
448 l_ConfigDirOverride = strdup(ConfigDirOverride);
449 if (l_ConfigDirOverride == NULL)
450 return M64ERR_NO_MEMORY;
451 }
452
453 /* get the full pathname to the config file and try to open it */
454 configpath = ConfigGetUserConfigPath();
455 if (configpath == NULL)
456 return M64ERR_FILES;
457
458 filepath = combinepath(configpath, MUPEN64PLUS_CFG_NAME);
459 if (filepath == NULL)
460 return M64ERR_NO_MEMORY;
461
462 fPtr = fopen(filepath, "rb");
463 if (fPtr == NULL)
464 {
465 DebugMessage(M64MSG_INFO, "Couldn't open configuration file '%s'. Using defaults.", filepath);
466 free(filepath);
467 l_SaveConfigOnExit = 1; /* auto-save the config file so that the defaults will be saved to disk */
468 return M64ERR_SUCCESS;
469 }
470 free(filepath);
471
472 /* read the entire config file */
473 fseek(fPtr, 0L, SEEK_END);
474 filelen = ftell(fPtr);
475 fseek(fPtr, 0L, SEEK_SET);
476
477 configtext = (char *) malloc(filelen + 1);
478 if (configtext == NULL)
479 {
480 fclose(fPtr);
481 return M64ERR_NO_MEMORY;
482 }
483 if (fread(configtext, 1, filelen, fPtr) != filelen)
484 {
485 free(configtext);
486 fclose(fPtr);
487 return M64ERR_FILES;
488 }
489 fclose(fPtr);
490
491 /* parse the file data */
492 current_section = NULL;
493 line = configtext;
494 end = configtext + filelen;
495 lastcomment = NULL;
496 *end = 0;
497 while (line < end)
498 {
499 ini_line l = ini_parse_line(&line);
500 switch (l.type)
501 {
502 case INI_COMMENT:
503 lastcomment = l.value;
504 break;
505
506 case INI_SECTION:
507 rval = ConfigOpenSection(l.name, (m64p_handle *) &current_section);
508 if (rval != M64ERR_SUCCESS)
509 {
510 free(configtext);
511 return rval;
512 }
513 lastcomment = NULL;
514 break;
515
516 case INI_PROPERTY:
517 if (l.value[0] == '"' && l.value[strlen(l.value)-1] == '"')
518 {
519 l.value++;
520 l.value[strlen(l.value)-1] = 0;
521 ConfigSetDefaultString((m64p_handle) current_section, l.name, l.value, lastcomment);
522 }
523 else if (osal_insensitive_strcmp(l.value, "false") == 0)
524 {
525 ConfigSetDefaultBool((m64p_handle) current_section, l.name, 0, lastcomment);
526 }
527 else if (osal_insensitive_strcmp(l.value, "true") == 0)
528 {
529 ConfigSetDefaultBool((m64p_handle) current_section, l.name, 1, lastcomment);
530 }
531 else if (is_numeric(l.value))
532 {
533 int val_int = (int) strtol(l.value, NULL, 10);
534 float val_float = (float) strtod(l.value, NULL);
535 if ((val_float - val_int) != 0.0)
536 ConfigSetDefaultFloat((m64p_handle) current_section, l.name, val_float, lastcomment);
537 else
538 ConfigSetDefaultInt((m64p_handle) current_section, l.name, val_int, lastcomment);
539 }
540 else
541 {
542 /* assume that it's a string */
543 ConfigSetDefaultString((m64p_handle) current_section, l.name, l.value, lastcomment);
544 }
545 lastcomment = NULL;
546 break;
547
548 default:
549 break;
550 }
551 }
552
553 /* release memory used for config file text */
554 free(configtext);
555
556 /* duplicate the entire config data list, to store a copy of the list which represents the state of the file on disk */
557 copy_configlist_active_to_saved();
558
559 return M64ERR_SUCCESS;
560}
561
562m64p_error ConfigShutdown(void)
563{
564 /* first, save the file if necessary */
565 if (l_SaveConfigOnExit)
566 ConfigSaveFile();
567
568 /* reset the initialized flag */
569 if (!l_ConfigInit)
570 return M64ERR_NOT_INIT;
571 l_ConfigInit = 0;
572
573 /* free any malloc'd local variables */
574 if (l_DataDirOverride != NULL)
575 {
576 free(l_DataDirOverride);
577 l_DataDirOverride = NULL;
578 }
579 if (l_ConfigDirOverride != NULL)
580 {
581 free(l_ConfigDirOverride);
582 l_ConfigDirOverride = NULL;
583 }
584
585 /* free all of the memory in the 2 lists */
586 delete_list(&l_ConfigListActive);
587 delete_list(&l_ConfigListSaved);
588
589 return M64ERR_SUCCESS;
590}
591
592/* ------------------------------------------------ */
593/* Selector functions, exported outside of the Core */
594/* ------------------------------------------------ */
595
596EXPORT m64p_error CALL ConfigListSections(void *context, void (*SectionListCallback)(void * context, const char * SectionName))
597{
598 config_section *curr_section;
599
600 if (!l_ConfigInit)
601 return M64ERR_NOT_INIT;
602 if (SectionListCallback == NULL)
603 return M64ERR_INPUT_ASSERT;
604
605 /* just walk through the section list, making a callback for each section name */
606 curr_section = l_ConfigListActive;
607 while (curr_section != NULL)
608 {
609 (*SectionListCallback)(context, curr_section->name);
610 curr_section = curr_section->next;
611 }
612
613 return M64ERR_SUCCESS;
614}
615
616EXPORT m64p_error CALL ConfigOpenSection(const char *SectionName, m64p_handle *ConfigSectionHandle)
617{
618 config_section **curr_section;
619 config_section *new_section;
620
621 if (!l_ConfigInit)
622 return M64ERR_NOT_INIT;
623 if (SectionName == NULL || ConfigSectionHandle == NULL)
624 return M64ERR_INPUT_ASSERT;
625
626 /* walk through the section list, looking for a case-insensitive name match */
627 curr_section = find_alpha_section_link(&l_ConfigListActive, SectionName);
628 if (*curr_section != NULL && osal_insensitive_strcmp(SectionName, (*curr_section)->name) == 0)
629 {
630 *ConfigSectionHandle = *curr_section;
631 return M64ERR_SUCCESS;
632 }
633
634 /* didn't find the section, so create new one */
635 new_section = config_section_create(SectionName);
636 if (new_section == NULL)
637 return M64ERR_NO_MEMORY;
638
639 /* add section to list in alphabetical order */
640 new_section->next = *curr_section;
641 *curr_section = new_section;
642
643 *ConfigSectionHandle = new_section;
644 return M64ERR_SUCCESS;
645}
646
647EXPORT m64p_error CALL ConfigListParameters(m64p_handle ConfigSectionHandle, void *context, void (*ParameterListCallback)(void * context, const char *ParamName, m64p_type ParamType))
648{
649 config_section *section;
650 config_var *curr_var;
651
652 if (!l_ConfigInit)
653 return M64ERR_NOT_INIT;
654 if (ConfigSectionHandle == NULL || ParameterListCallback == NULL)
655 return M64ERR_INPUT_ASSERT;
656
657 section = (config_section *) ConfigSectionHandle;
658 if (section->magic != SECTION_MAGIC)
659 return M64ERR_INPUT_INVALID;
660
661 /* walk through this section's parameter list, making a callback for each parameter */
662 curr_var = section->first_var;
663 while (curr_var != NULL)
664 {
665 (*ParameterListCallback)(context, curr_var->name, curr_var->type);
666 curr_var = curr_var->next;
667 }
668
669 return M64ERR_SUCCESS;
670}
671
672EXPORT int CALL ConfigHasUnsavedChanges(const char *SectionName)
673{
674 config_section *input_section, *curr_section;
675 config_var *active_var, *saved_var;
676
677 /* check input conditions */
678 if (!l_ConfigInit)
679 {
680 DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Core config not initialized!");
681 return 0;
682 }
683
684 /* if SectionName is NULL or blank, then check all sections */
685 if (SectionName == NULL || strlen(SectionName) < 1)
686 {
687 int iNumActiveSections = 0, iNumSavedSections = 0;
688 /* first, search through all sections in Active list. Recursively call ourself and return 1 if changed */
689 curr_section = l_ConfigListActive;
690 while (curr_section != NULL)
691 {
692 if (ConfigHasUnsavedChanges(curr_section->name))
693 return 1;
694 curr_section = curr_section->next;
695 iNumActiveSections++;
696 }
697 /* Next, count the number of Saved sections and see if the count matches */
698 curr_section = l_ConfigListSaved;
699 while (curr_section != NULL)
700 {
701 curr_section = curr_section->next;
702 iNumSavedSections++;
703 }
704 if (iNumActiveSections == iNumSavedSections)
705 return 0; /* no changes */
706 else
707 return 1;
708 }
709
710 /* walk through the Active section list, looking for a case-insensitive name match with input string */
711 input_section = find_section(l_ConfigListActive, SectionName);
712 if (input_section == NULL)
713 {
714 DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): section name '%s' not found!", SectionName);
715 return 0;
716 }
717
718 /* walk through the Saved section list, looking for a case-insensitive name match */
719 curr_section = find_section(l_ConfigListSaved, SectionName);
720 if (curr_section == NULL)
721 {
722 /* if this section isn't present in saved list, then it has been newly created */
723 return 1;
724 }
725
726 /* compare all of the variables in the two sections. They are expected to be in the same order */
727 active_var = input_section->first_var;
728 saved_var = curr_section->first_var;
729 while (active_var != NULL && saved_var != NULL)
730 {
731 if (strcmp(active_var->name, saved_var->name) != 0)
732 return 1;
733 if (active_var->type != saved_var->type)
734 return 1;
735 switch(active_var->type)
736 {
737 case M64TYPE_INT:
738 if (active_var->val.integer != saved_var->val.integer)
739 return 1;
740 break;
741 case M64TYPE_FLOAT:
742 if (active_var->val.number != saved_var->val.number)
743 return 1;
744 break;
745 case M64TYPE_BOOL:
746 if ((active_var->val.integer != 0) != (saved_var->val.integer != 0))
747 return 1;
748 break;
749 case M64TYPE_STRING:
750 if (active_var->val.string == NULL)
751 {
752 DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Variable '%s' NULL Active string pointer!", active_var->name);
753 return 1;
754 }
755 if (saved_var->val.string == NULL)
756 {
757 DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Variable '%s' NULL Saved string pointer!", active_var->name);
758 return 1;
759 }
760 if (strcmp(active_var->val.string, saved_var->val.string) != 0)
761 return 1;
762 break;
763 default:
764 DebugMessage(M64MSG_ERROR, "ConfigHasUnsavedChanges(): Invalid variable '%s' type %i!", active_var->name, active_var->type);
765 return 1;
766 }
767 if (active_var->comment != NULL && saved_var->comment != NULL && strcmp(active_var->comment, saved_var->comment) != 0)
768 return 1;
769 active_var = active_var->next;
770 saved_var = saved_var->next;
771 }
772
773 /* any extra new variables on the end, or deleted variables? */
774 if (active_var != NULL || saved_var != NULL)
775 return 1;
776
777 /* exactly the same */
778 return 0;
779}
780
781/* ------------------------------------------------------- */
782/* Modifier functions, exported outside of the Core */
783/* ------------------------------------------------------- */
784
785EXPORT m64p_error CALL ConfigDeleteSection(const char *SectionName)
786{
787 config_section **curr_section_link;
788 config_section *next_section;
789
790 if (!l_ConfigInit)
791 return M64ERR_NOT_INIT;
792 if (l_ConfigListActive == NULL)
793 return M64ERR_INPUT_NOT_FOUND;
794
795 /* find the named section and pull it out of the list */
796 curr_section_link = find_section_link(&l_ConfigListActive, SectionName);
797 if (*curr_section_link == NULL)
798 return M64ERR_INPUT_NOT_FOUND;
799
800 next_section = (*curr_section_link)->next;
801
802 /* delete the named section */
803 delete_section(*curr_section_link);
804
805 /* fix the pointer to point to the next section after the deleted one */
806 *curr_section_link = next_section;
807
808 return M64ERR_SUCCESS;
809}
810
811EXPORT m64p_error CALL ConfigSaveFile(void)
812{
813 if (!l_ConfigInit)
814 return M64ERR_NOT_INIT;
815
816 /* copy the active config list to the saved config list */
817 copy_configlist_active_to_saved();
818
819 /* write the saved config list out to a file */
820 return (write_configlist_file());
821}
822
823EXPORT m64p_error CALL ConfigSaveSection(const char *SectionName)
824{
825 config_section *curr_section, *new_section;
826 config_section **insertion_point;
827
828 if (!l_ConfigInit)
829 return M64ERR_NOT_INIT;
830 if (SectionName == NULL || strlen(SectionName) < 1)
831 return M64ERR_INPUT_ASSERT;
832
833 /* walk through the Active section list, looking for a case-insensitive name match */
834 curr_section = find_section(l_ConfigListActive, SectionName);
835 if (curr_section == NULL)
836 return M64ERR_INPUT_NOT_FOUND;
837
838 /* duplicate this section */
839 new_section = section_deepcopy(curr_section);
840 if (new_section == NULL)
841 return M64ERR_NO_MEMORY;
842
843 /* update config section that's in the Saved list with the new one */
844 insertion_point = find_alpha_section_link(&l_ConfigListSaved, SectionName);
845 if (*insertion_point != NULL && osal_insensitive_strcmp((*insertion_point)->name, SectionName) == 0)
846 {
847 /* the section exists in the saved list and will be replaced */
848 new_section->next = (*insertion_point)->next;
849 delete_section(*insertion_point);
850 *insertion_point = new_section;
851 }
852 else
853 {
854 /* the section didn't exist in the saved list and has to be inserted */
855 new_section->next = *insertion_point;
856 *insertion_point = new_section;
857 }
858
859 /* write the saved config list out to a file */
860 return (write_configlist_file());
861}
862
863EXPORT m64p_error CALL ConfigRevertChanges(const char *SectionName)
864{
865 config_section **active_section_link, *active_section, *saved_section, *new_section;
866
867 /* check input conditions */
868 if (!l_ConfigInit)
869 return M64ERR_NOT_INIT;
870 if (SectionName == NULL)
871 return M64ERR_INPUT_ASSERT;
872
873 /* walk through the Active section list, looking for a case-insensitive name match with input string */
874 active_section_link = find_section_link(&l_ConfigListActive, SectionName);
875 active_section = *active_section_link;
876 if (active_section == NULL)
877 return M64ERR_INPUT_NOT_FOUND;
878
879 /* walk through the Saved section list, looking for a case-insensitive name match */
880 saved_section = find_section(l_ConfigListSaved, SectionName);
881 if (saved_section == NULL)
882 {
883 /* if this section isn't present in saved list, then it has been newly created */
884 return M64ERR_INPUT_NOT_FOUND;
885 }
886
887 /* copy the section as it is on the disk */
888 new_section = section_deepcopy(saved_section);
889 if (new_section == NULL)
890 return M64ERR_NO_MEMORY;
891
892 /* replace active_section with saved_section in the linked list */
893 *active_section_link = new_section;
894 new_section->next = active_section->next;
895
896 /* release memory associated with active_section */
897 delete_section(active_section);
898
899 return M64ERR_SUCCESS;
900}
901
902
903/* ------------------------------------------------------- */
904/* Generic Get/Set functions, exported outside of the Core */
905/* ------------------------------------------------------- */
906
907EXPORT m64p_error CALL ConfigSetParameter(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type ParamType, const void *ParamValue)
908{
909 config_section *section;
910 config_var *var;
911
912 /* check input conditions */
913 if (!l_ConfigInit)
914 return M64ERR_NOT_INIT;
915 if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL || (int) ParamType < 1 || (int) ParamType > 4)
916 return M64ERR_INPUT_ASSERT;
917
918 section = (config_section *) ConfigSectionHandle;
919 if (section->magic != SECTION_MAGIC)
920 return M64ERR_INPUT_INVALID;
921
922 /* if this parameter doesn't already exist, then create it and add it to the section */
923 var = find_section_var(section, ParamName);
924 if (var == NULL)
925 {
926 var = config_var_create(ParamName, NULL);
927 if (var == NULL)
928 return M64ERR_NO_MEMORY;
929 append_var_to_section(section, var);
930 }
931
932 /* cleanup old values */
933 switch (var->type)
934 {
935 case M64TYPE_STRING:
936 free(var->val.string);
937 break;
938 default:
939 break;
940 }
941
942 /* set this parameter's value */
943 var->type = ParamType;
944 switch(ParamType)
945 {
946 case M64TYPE_INT:
947 var->val.integer = *((int *) ParamValue);
948 break;
949 case M64TYPE_FLOAT:
950 var->val.number = *((float *) ParamValue);
951 break;
952 case M64TYPE_BOOL:
953 var->val.integer = (*((int *) ParamValue) != 0);
954 break;
955 case M64TYPE_STRING:
956 var->val.string = strdup((char *)ParamValue);
957 if (var->val.string == NULL)
958 return M64ERR_NO_MEMORY;
959 break;
960 default:
961 /* this is logically impossible because of the ParamType check at the top of this function */
962 break;
963 }
964
965 return M64ERR_SUCCESS;
966}
967
968EXPORT m64p_error CALL ConfigGetParameter(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type ParamType, void *ParamValue, int MaxSize)
969{
970 config_section *section;
971 config_var *var;
972
973 /* check input conditions */
974 if (!l_ConfigInit)
975 return M64ERR_NOT_INIT;
976 if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL || (int) ParamType < 1 || (int) ParamType > 4)
977 return M64ERR_INPUT_ASSERT;
978
979 section = (config_section *) ConfigSectionHandle;
980 if (section->magic != SECTION_MAGIC)
981 return M64ERR_INPUT_INVALID;
982
983 /* if this parameter doesn't already exist, return an error */
984 var = find_section_var(section, ParamName);
985 if (var == NULL)
986 return M64ERR_INPUT_NOT_FOUND;
987
988 /* call the specific Get function to translate the parameter to the desired type */
989 switch(ParamType)
990 {
991 case M64TYPE_INT:
992 if (MaxSize < sizeof(int)) return M64ERR_INPUT_INVALID;
993 if (var->type != M64TYPE_INT && var->type != M64TYPE_FLOAT) return M64ERR_WRONG_TYPE;
994 *((int *) ParamValue) = ConfigGetParamInt(ConfigSectionHandle, ParamName);
995 break;
996 case M64TYPE_FLOAT:
997 if (MaxSize < sizeof(float)) return M64ERR_INPUT_INVALID;
998 if (var->type != M64TYPE_INT && var->type != M64TYPE_FLOAT) return M64ERR_WRONG_TYPE;
999 *((float *) ParamValue) = ConfigGetParamFloat(ConfigSectionHandle, ParamName);
1000 break;
1001 case M64TYPE_BOOL:
1002 if (MaxSize < sizeof(int)) return M64ERR_INPUT_INVALID;
1003 if (var->type != M64TYPE_BOOL && var->type != M64TYPE_INT) return M64ERR_WRONG_TYPE;
1004 *((int *) ParamValue) = ConfigGetParamBool(ConfigSectionHandle, ParamName);
1005 break;
1006 case M64TYPE_STRING:
1007 {
1008 const char *string;
1009 if (MaxSize < 1) return M64ERR_INPUT_INVALID;
1010 if (var->type != M64TYPE_STRING && var->type != M64TYPE_BOOL) return M64ERR_WRONG_TYPE;
1011 string = ConfigGetParamString(ConfigSectionHandle, ParamName);
1012 strncpy((char *) ParamValue, string, MaxSize);
1013 *((char *) ParamValue + MaxSize - 1) = 0;
1014 break;
1015 }
1016 default:
1017 /* this is logically impossible because of the ParamType check at the top of this function */
1018 break;
1019 }
1020
1021 return M64ERR_SUCCESS;
1022}
1023
1024EXPORT m64p_error CALL ConfigGetParameterType(m64p_handle ConfigSectionHandle, const char *ParamName, m64p_type *ParamType)
1025{
1026 config_section *section;
1027 config_var *var;
1028
1029 /* check input conditions */
1030 if (!l_ConfigInit)
1031 return M64ERR_NOT_INIT;
1032 if (ConfigSectionHandle == NULL || ParamName == NULL || ParamType == NULL)
1033 return M64ERR_INPUT_ASSERT;
1034
1035 section = (config_section *) ConfigSectionHandle;
1036 if (section->magic != SECTION_MAGIC)
1037 return M64ERR_INPUT_INVALID;
1038
1039 /* if this parameter doesn't already exist, return an error */
1040 var = find_section_var(section, ParamName);
1041 if (var == NULL)
1042 return M64ERR_INPUT_NOT_FOUND;
1043
1044 *ParamType = var->type;
1045 return M64ERR_SUCCESS;
1046}
1047
1048
1049EXPORT const char * CALL ConfigGetParameterHelp(m64p_handle ConfigSectionHandle, const char *ParamName)
1050{
1051 config_section *section;
1052 config_var *var;
1053
1054 /* check input conditions */
1055 if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL)
1056 return NULL;
1057
1058 section = (config_section *) ConfigSectionHandle;
1059 if (section->magic != SECTION_MAGIC)
1060 return NULL;
1061
1062 /* if this parameter doesn't exist, return an error */
1063 var = find_section_var(section, ParamName);
1064 if (var == NULL)
1065 return NULL;
1066
1067 return var->comment;
1068}
1069
1070/* ------------------------------------------------------- */
1071/* Special Get/Set functions, exported outside of the Core */
1072/* ------------------------------------------------------- */
1073
1074EXPORT m64p_error CALL ConfigSetDefaultInt(m64p_handle ConfigSectionHandle, const char *ParamName, int ParamValue, const char *ParamHelp)
1075{
1076 config_section *section;
1077 config_var *var;
1078
1079 /* check input conditions */
1080 if (!l_ConfigInit)
1081 return M64ERR_NOT_INIT;
1082 if (ConfigSectionHandle == NULL || ParamName == NULL)
1083 return M64ERR_INPUT_ASSERT;
1084
1085 section = (config_section *) ConfigSectionHandle;
1086 if (section->magic != SECTION_MAGIC)
1087 return M64ERR_INPUT_INVALID;
1088
1089 /* if this parameter already exists, then just return successfully */
1090 var = find_section_var(section, ParamName);
1091 if (var != NULL)
1092 return M64ERR_SUCCESS;
1093
1094 /* otherwise create a new config_var object and add it to this section */
1095 var = config_var_create(ParamName, ParamHelp);
1096 if (var == NULL)
1097 return M64ERR_NO_MEMORY;
1098 var->type = M64TYPE_INT;
1099 var->val.integer = ParamValue;
1100 append_var_to_section(section, var);
1101
1102 return M64ERR_SUCCESS;
1103}
1104
1105EXPORT m64p_error CALL ConfigSetDefaultFloat(m64p_handle ConfigSectionHandle, const char *ParamName, float ParamValue, const char *ParamHelp)
1106{
1107 config_section *section;
1108 config_var *var;
1109
1110 /* check input conditions */
1111 if (!l_ConfigInit)
1112 return M64ERR_NOT_INIT;
1113 if (ConfigSectionHandle == NULL || ParamName == NULL)
1114 return M64ERR_INPUT_ASSERT;
1115
1116 section = (config_section *) ConfigSectionHandle;
1117 if (section->magic != SECTION_MAGIC)
1118 return M64ERR_INPUT_INVALID;
1119
1120 /* if this parameter already exists, then just return successfully */
1121 var = find_section_var(section, ParamName);
1122 if (var != NULL)
1123 return M64ERR_SUCCESS;
1124
1125 /* otherwise create a new config_var object and add it to this section */
1126 var = config_var_create(ParamName, ParamHelp);
1127 if (var == NULL)
1128 return M64ERR_NO_MEMORY;
1129 var->type = M64TYPE_FLOAT;
1130 var->val.number = ParamValue;
1131 append_var_to_section(section, var);
1132
1133 return M64ERR_SUCCESS;
1134}
1135
1136EXPORT m64p_error CALL ConfigSetDefaultBool(m64p_handle ConfigSectionHandle, const char *ParamName, int ParamValue, const char *ParamHelp)
1137{
1138 config_section *section;
1139 config_var *var;
1140
1141 /* check input conditions */
1142 if (!l_ConfigInit)
1143 return M64ERR_NOT_INIT;
1144 if (ConfigSectionHandle == NULL || ParamName == NULL)
1145 return M64ERR_INPUT_ASSERT;
1146
1147 section = (config_section *) ConfigSectionHandle;
1148 if (section->magic != SECTION_MAGIC)
1149 return M64ERR_INPUT_INVALID;
1150
1151 /* if this parameter already exists, then just return successfully */
1152 var = find_section_var(section, ParamName);
1153 if (var != NULL)
1154 return M64ERR_SUCCESS;
1155
1156 /* otherwise create a new config_var object and add it to this section */
1157 var = config_var_create(ParamName, ParamHelp);
1158 if (var == NULL)
1159 return M64ERR_NO_MEMORY;
1160 var->type = M64TYPE_BOOL;
1161 var->val.integer = ParamValue ? 1 : 0;
1162 append_var_to_section(section, var);
1163
1164 return M64ERR_SUCCESS;
1165}
1166
1167EXPORT m64p_error CALL ConfigSetDefaultString(m64p_handle ConfigSectionHandle, const char *ParamName, const char * ParamValue, const char *ParamHelp)
1168{
1169 config_section *section;
1170 config_var *var;
1171
1172 /* check input conditions */
1173 if (!l_ConfigInit)
1174 return M64ERR_NOT_INIT;
1175 if (ConfigSectionHandle == NULL || ParamName == NULL || ParamValue == NULL)
1176 return M64ERR_INPUT_ASSERT;
1177
1178 section = (config_section *) ConfigSectionHandle;
1179 if (section->magic != SECTION_MAGIC)
1180 return M64ERR_INPUT_INVALID;
1181
1182 /* if this parameter already exists, then just return successfully */
1183 var = find_section_var(section, ParamName);
1184 if (var != NULL)
1185 return M64ERR_SUCCESS;
1186
1187 /* otherwise create a new config_var object and add it to this section */
1188 var = config_var_create(ParamName, ParamHelp);
1189 if (var == NULL)
1190 return M64ERR_NO_MEMORY;
1191 var->type = M64TYPE_STRING;
1192 var->val.string = strdup(ParamValue);
1193 if (var->val.string == NULL)
1194 {
1195 delete_var(var);
1196 return M64ERR_NO_MEMORY;
1197 }
1198 append_var_to_section(section, var);
1199
1200 return M64ERR_SUCCESS;
1201}
1202
1203EXPORT int CALL ConfigGetParamInt(m64p_handle ConfigSectionHandle, const char *ParamName)
1204{
1205 config_section *section;
1206 config_var *var;
1207
1208 /* check input conditions */
1209 if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL)
1210 {
1211 DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): Input assertion!");
1212 return 0;
1213 }
1214
1215 section = (config_section *) ConfigSectionHandle;
1216 if (section->magic != SECTION_MAGIC)
1217 {
1218 DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): ConfigSectionHandle invalid!");
1219 return 0;
1220 }
1221
1222 /* if this parameter doesn't already exist, return an error */
1223 var = find_section_var(section, ParamName);
1224 if (var == NULL)
1225 {
1226 DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): Parameter '%s' not found!", ParamName);
1227 return 0;
1228 }
1229
1230 /* translate the actual variable type to an int */
1231 switch(var->type)
1232 {
1233 case M64TYPE_INT:
1234 return var->val.integer;
1235 case M64TYPE_FLOAT:
1236 return (int) var->val.number;
1237 case M64TYPE_BOOL:
1238 return (var->val.integer != 0);
1239 case M64TYPE_STRING:
1240 return atoi(var->val.string);
1241 default:
1242 DebugMessage(M64MSG_ERROR, "ConfigGetParamInt(): invalid internal parameter type for '%s'", ParamName);
1243 return 0;
1244 }
1245
1246 return 0;
1247}
1248
1249EXPORT float CALL ConfigGetParamFloat(m64p_handle ConfigSectionHandle, const char *ParamName)
1250{
1251 config_section *section;
1252 config_var *var;
1253
1254 /* check input conditions */
1255 if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL)
1256 {
1257 DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): Input assertion!");
1258 return 0.0;
1259 }
1260
1261 section = (config_section *) ConfigSectionHandle;
1262 if (section->magic != SECTION_MAGIC)
1263 {
1264 DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): ConfigSectionHandle invalid!");
1265 return 0.0;
1266 }
1267
1268 /* if this parameter doesn't already exist, return an error */
1269 var = find_section_var(section, ParamName);
1270 if (var == NULL)
1271 {
1272 DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): Parameter '%s' not found!", ParamName);
1273 return 0.0;
1274 }
1275
1276 /* translate the actual variable type to an int */
1277 switch(var->type)
1278 {
1279 case M64TYPE_INT:
1280 return (float) var->val.integer;
1281 case M64TYPE_FLOAT:
1282 return var->val.number;
1283 case M64TYPE_BOOL:
1284 return (var->val.integer != 0) ? 1.0f : 0.0f;
1285 case M64TYPE_STRING:
1286 return (float) atof(var->val.string);
1287 default:
1288 DebugMessage(M64MSG_ERROR, "ConfigGetParamFloat(): invalid internal parameter type for '%s'", ParamName);
1289 return 0.0;
1290 }
1291
1292 return 0.0;
1293}
1294
1295EXPORT int CALL ConfigGetParamBool(m64p_handle ConfigSectionHandle, const char *ParamName)
1296{
1297 config_section *section;
1298 config_var *var;
1299
1300 /* check input conditions */
1301 if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL)
1302 {
1303 DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): Input assertion!");
1304 return 0;
1305 }
1306
1307 section = (config_section *) ConfigSectionHandle;
1308 if (section->magic != SECTION_MAGIC)
1309 {
1310 DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): ConfigSectionHandle invalid!");
1311 return 0;
1312 }
1313
1314 /* if this parameter doesn't already exist, return an error */
1315 var = find_section_var(section, ParamName);
1316 if (var == NULL)
1317 {
1318 DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): Parameter '%s' not found!", ParamName);
1319 return 0;
1320 }
1321
1322 /* translate the actual variable type to an int */
1323 switch(var->type)
1324 {
1325 case M64TYPE_INT:
1326 return (var->val.integer != 0);
1327 case M64TYPE_FLOAT:
1328 return (var->val.number != 0.0);
1329 case M64TYPE_BOOL:
1330 return var->val.integer;
1331 case M64TYPE_STRING:
1332 return (osal_insensitive_strcmp(var->val.string, "true") == 0);
1333 default:
1334 DebugMessage(M64MSG_ERROR, "ConfigGetParamBool(): invalid internal parameter type for '%s'", ParamName);
1335 return 0;
1336 }
1337
1338 return 0;
1339}
1340
1341EXPORT const char * CALL ConfigGetParamString(m64p_handle ConfigSectionHandle, const char *ParamName)
1342{
1343 static char outstr[64]; /* warning: not thread safe */
1344 config_section *section;
1345 config_var *var;
1346
1347 /* check input conditions */
1348 if (!l_ConfigInit || ConfigSectionHandle == NULL || ParamName == NULL)
1349 {
1350 DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): Input assertion!");
1351 return "";
1352 }
1353
1354 section = (config_section *) ConfigSectionHandle;
1355 if (section->magic != SECTION_MAGIC)
1356 {
1357 DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): ConfigSectionHandle invalid!");
1358 return "";
1359 }
1360
1361 /* if this parameter doesn't already exist, return an error */
1362 var = find_section_var(section, ParamName);
1363 if (var == NULL)
1364 {
1365 DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): Parameter '%s' not found!", ParamName);
1366 return "";
1367 }
1368
1369 /* translate the actual variable type to an int */
1370 switch(var->type)
1371 {
1372 case M64TYPE_INT:
1373 snprintf(outstr, 63, "%i", var->val.integer);
1374 outstr[63] = 0;
1375 return outstr;
1376 case M64TYPE_FLOAT:
1377 snprintf(outstr, 63, "%f", var->val.number);
1378 outstr[63] = 0;
1379 return outstr;
1380 case M64TYPE_BOOL:
1381 return (var->val.integer ? "True" : "False");
1382 case M64TYPE_STRING:
1383 return var->val.string;
1384 default:
1385 DebugMessage(M64MSG_ERROR, "ConfigGetParamString(): invalid internal parameter type for '%s'", ParamName);
1386 return "";
1387 }
1388
1389 return "";
1390}
1391
1392/* ------------------------------------------------------ */
1393/* OS Abstraction functions, exported outside of the Core */
1394/* ------------------------------------------------------ */
1395
1396EXPORT const char * CALL ConfigGetSharedDataFilepath(const char *filename)
1397{
1398 const char *configsharepath = NULL;
1399 m64p_handle CoreHandle = NULL;
1400
1401 /* check input parameter */
1402 if (filename == NULL) return NULL;
1403
1404 /* try to get the SharedDataPath string variable in the Core configuration section */
1405 if (ConfigOpenSection("Core", &CoreHandle) == M64ERR_SUCCESS)
1406 {
1407 configsharepath = ConfigGetParamString(CoreHandle, "SharedDataPath");
1408 }
1409
1410 return osal_get_shared_filepath(filename, l_DataDirOverride, configsharepath);
1411}
1412
1413EXPORT const char * CALL ConfigGetUserConfigPath(void)
1414{
1415 if (l_ConfigDirOverride != NULL)
1416 {
1417 osal_mkdirp(l_ConfigDirOverride, 0700);
1418 return l_ConfigDirOverride;
1419 }
1420 else
1421 return osal_get_user_configpath();
1422}
1423
1424EXPORT const char * CALL ConfigGetUserDataPath(void)
1425{
1426 return osal_get_user_datapath();
1427}
1428
1429EXPORT const char * CALL ConfigGetUserCachePath(void)
1430{
1431 return osal_get_user_cachepath();
1432}
1433