d5f583d94caae25969fb6486f1fece795091010e
[picodrive.git] / platform / common / config_file.c
1 /*
2  * Human-readable config file management for PicoDrive
3  * (C) notaz, 2008-2010
4  *
5  * This work is licensed under the terms of MAME license.
6  * See COPYING file in the top-level directory.
7  */
8
9 #include <stdio.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #ifdef __EPOC32__
13 #include <unistd.h>
14 #endif
15
16 #include "../libpicofe/input.h"
17 #include "../libpicofe/plat.h"
18 #include "../libpicofe/lprintf.h"
19 #include "config_file.h"
20
21 static char *mystrip(char *str);
22
23 #ifndef _MSC_VER
24
25 #include "menu_pico.h"
26 #include "emu.h"
27 #include <pico/pico_int.h>
28
29 // always output DOS endlines
30 #ifdef _WIN32
31 #define NL "\n"
32 #else
33 #define NL "\r\n"
34 #endif
35
36 static int seek_sect(FILE *f, const char *section)
37 {
38         char line[640], *tmp;
39         int len;
40
41         len = strlen(section);
42         // seek to the section needed
43         while (!feof(f))
44         {
45                 tmp = fgets(line, sizeof(line), f);
46                 if (tmp == NULL) break;
47
48                 if (line[0] != '[') continue; // not section start
49                 if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']')
50                         return 1; // found it
51         }
52
53         return 0;
54 }
55
56 static void keys_write(FILE *fn, int dev_id, const int *binds)
57 {
58         char act[48];
59         int key_count = 0, k, i;
60
61         in_get_config(dev_id, IN_CFG_BIND_COUNT, &key_count);
62
63         for (k = 0; k < key_count; k++)
64         {
65                 const char *name;
66                 int mask;
67                 act[0] = act[31] = 0;
68
69                 name = in_get_key_name(dev_id, k);
70
71                 for (i = 0; me_ctrl_actions[i].name != NULL; i++) {
72                         mask = me_ctrl_actions[i].mask;
73                         if (mask & binds[IN_BIND_OFFS(k, IN_BINDTYPE_PLAYER12)]) {
74                                 strncpy(act, me_ctrl_actions[i].name, 31);
75                                 fprintf(fn, "bind %s = player1 %s" NL, name, mystrip(act));
76                         }
77                         mask = me_ctrl_actions[i].mask << 16;
78                         if (mask & binds[IN_BIND_OFFS(k, IN_BINDTYPE_PLAYER12)]) {
79                                 strncpy(act, me_ctrl_actions[i].name, 31);
80                                 fprintf(fn, "bind %s = player2 %s" NL, name, mystrip(act));
81                         }
82                 }
83
84                 for (i = 0; me_ctrl_actions[i].name != NULL; i++) {
85                         mask = me_ctrl_actions[i].mask;
86                         if (mask & binds[IN_BIND_OFFS(k, IN_BINDTYPE_PLAYER34)]) {
87                                 strncpy(act, me_ctrl_actions[i].name, 31);
88                                 fprintf(fn, "bind %s = player3 %s" NL, name, mystrip(act));
89                         }
90                         mask = me_ctrl_actions[i].mask << 16;
91                         if (mask & binds[IN_BIND_OFFS(k, IN_BINDTYPE_PLAYER34)]) {
92                                 strncpy(act, me_ctrl_actions[i].name, 31);
93                                 fprintf(fn, "bind %s = player4 %s" NL, name, mystrip(act));
94                         }
95                 }
96
97                 for (i = 0; emuctrl_actions[i].name != NULL; i++) {
98                         mask = emuctrl_actions[i].mask;
99                         if (mask & binds[IN_BIND_OFFS(k, IN_BINDTYPE_EMU)]) {
100                                 strncpy(act, emuctrl_actions[i].name, 31);
101                                 fprintf(fn, "bind %s = %s" NL, name, mystrip(act));
102                         }
103                 }
104         }
105 }
106
107 int config_write(const char *fname)
108 {
109         FILE *fn = NULL;
110         menu_entry *me;
111         int t;
112         char line[640];
113
114         fn = fopen(fname, "w");
115         if (fn == NULL)
116                 return -1;
117
118         for (me = me_list_get_first(); me != NULL; me = me_list_get_next())
119         {
120                 int dummy;
121                 if (!me->need_to_save)
122                         continue;
123                 if (me->name == NULL || me->name[0] == 0)
124                         continue;
125
126                 if (me->beh == MB_OPT_ONOFF || me->beh == MB_OPT_CUSTONOFF) {
127                         fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
128                 }
129                 else if (me->beh == MB_OPT_RANGE || me->beh == MB_OPT_CUSTRANGE) {
130                         fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
131                 }
132                 else if (me->beh == MB_OPT_ENUM) {
133                         const char **names = (const char **)me->data;
134                         if (names == NULL)
135                                 continue;
136                         for (t = 0; names[t] != NULL; t++) {
137                                 if (*(int *)me->var == t) {
138                                         strncpy(line, names[t], sizeof(line)-1);
139                                         line[sizeof(line)-1] = '\0';
140                                         goto write_line;
141                                 }
142                         }
143                 }
144                 else if (me->generate_name != NULL) {
145                         strncpy(line, me->generate_name(me->id, &dummy), sizeof(line)-1);
146                         line[sizeof(line)-1] = '\0';
147                         goto write_line;
148                 }
149                 else
150                         lprintf("config: unhandled write: '%s' id %d behavior %d\n",
151                                 me->name, me->id, me->beh);
152                 continue;
153
154 write_line:
155                 line[sizeof(line) - 1] = 0;
156                 mystrip(line);
157                 fprintf(fn, "%s = %s" NL, me->name, line);
158         }
159
160         /* input: save binds */
161         for (t = 0; t < IN_MAX_DEVS; t++)
162         {
163                 const int *binds = in_get_dev_binds(t);
164                 const char *name = in_get_dev_name(t, 0, 0);
165                 int count = 0;
166
167                 if (binds == NULL || name == NULL)
168                         continue;
169
170                 fprintf(fn, "binddev = %s" NL, name);
171
172                 in_get_config(t, IN_CFG_BIND_COUNT, &count);
173                 keys_write(fn, t, binds);
174         }
175
176         fprintf(fn, "Sound Volume = %i" NL, currentConfig.volume);
177
178         fprintf(fn, NL);
179
180         fclose(fn);
181         return 0;
182 }
183
184 int config_writelrom(const char *fname)
185 {
186         char line[640], *tmp, *optr = NULL;
187         char *old_data = NULL;
188         int size;
189         FILE *f;
190
191         if (strlen(rom_fname_loaded) == 0) return -1;
192
193         f = fopen(fname, "r");
194         if (f != NULL)
195         {
196                 fseek(f, 0, SEEK_END);
197                 size = ftell(f);
198                 fseek(f, 0, SEEK_SET);
199                 old_data = malloc(size + size/8);
200                 if (old_data != NULL)
201                 {
202                         optr = old_data;
203                         while (!feof(f))
204                         {
205                                 tmp = fgets(line, sizeof(line), f);
206                                 if (tmp == NULL) break;
207                                 mystrip(line);
208                                 if (strncasecmp(line, "LastUsedROM", 11) == 0)
209                                         continue;
210                                 sprintf(optr, "%s", line);
211                                 optr += strlen(optr);
212                         }
213                 }
214                 fclose(f);
215         }
216
217         f = fopen(fname, "w");
218         if (f == NULL) return -1;
219
220         if (old_data != NULL) {
221                 fwrite(old_data, 1, optr - old_data, f);
222                 free(old_data);
223         }
224         fprintf(f, "LastUsedROM = %s" NL, rom_fname_loaded);
225         fclose(f);
226         return 0;
227 }
228
229 /* --------------------------------------------------------------------------*/
230
231 int config_readlrom(const char *fname)
232 {
233         char line[640], *tmp;
234         int i, len, ret = -1;
235         FILE *f;
236
237         f = fopen(fname, "r");
238         if (f == NULL) return -1;
239
240         // seek to the section needed
241         while (!feof(f))
242         {
243                 tmp = fgets(line, sizeof(line), f);
244                 if (tmp == NULL) break;
245
246                 if (strncasecmp(line, "LastUsedROM", 11) != 0) continue;
247                 len = strlen(line);
248                 for (i = 0; i < len; i++)
249                         if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
250                 tmp = strchr(line, '=');
251                 if (tmp == NULL) break;
252                 tmp++;
253                 mystrip(tmp);
254
255                 len = sizeof(rom_fname_loaded)-1;
256                 strncpy(rom_fname_loaded, tmp, len);
257                 rom_fname_loaded[len] = 0;
258                 ret = 0;
259                 break;
260         }
261         fclose(f);
262         return ret;
263 }
264
265 static int custom_read(menu_entry *me, const char *var, const char *val)
266 {
267         char *tmp;
268
269         switch (me->id)
270         {
271                 case MA_OPT_FRAMESKIP:
272                         if (strcasecmp(var, "Frameskip") != 0) return 0;
273                         if (strcasecmp(val, "Auto") == 0)
274                              currentConfig.Frameskip = -1;
275                         else currentConfig.Frameskip = atoi(val);
276                         return 1;
277
278                 case MA_OPT_SOUND_QUALITY:
279                         if (strcasecmp(var, "Sound Quality") != 0) return 0;
280                         PicoIn.sndRate = strtoul(val, &tmp, 10);
281                         if (PicoIn.sndRate < 8000 || PicoIn.sndRate > 54000) {
282                                 if  (strncasecmp(tmp, "native", 6) == 0) {
283                                         tmp += 6;
284                                         PicoIn.sndRate = 53000;
285                                 } else
286                                         PicoIn.sndRate = 22050;
287                         }
288                         if (*tmp == 'H' || *tmp == 'h') tmp++;
289                         if (*tmp == 'Z' || *tmp == 'z') tmp++;
290                         while (*tmp == ' ') tmp++;
291                         if        (strcasecmp(tmp, "stereo") == 0) {
292                                 PicoIn.opt |=  POPT_EN_STEREO;
293                         } else if (strcasecmp(tmp, "mono") == 0) {
294                                 PicoIn.opt &= ~POPT_EN_STEREO;
295                         } else
296                                 return 0;
297                         return 1;
298
299                 case MA_OPT_SOUND_ALPHA:
300                         if (strcasecmp(var, "Filter strength") != 0) return 0;
301                         PicoIn.sndFilterAlpha = 0x10000 * atof(val);
302                         return 1;
303
304                 case MA_OPT_REGION:
305                         if (strcasecmp(var, "Region") != 0) return 0;
306                         if       (strncasecmp(val, "Auto: ", 6) == 0)
307                         {
308                                 const char *p = val + 5, *end = val + strlen(val);
309                                 int i;
310                                 PicoIn.regionOverride = PicoIn.autoRgnOrder = 0;
311                                 for (i = 0; p < end && i < 3; i++)
312                                 {
313                                         while (*p == ' ') p++;
314                                         if        (p[0] == 'J' && p[1] == 'P') {
315                                                 PicoIn.autoRgnOrder |= 1 << (i*4);
316                                         } else if (p[0] == 'U' && p[1] == 'S') {
317                                                 PicoIn.autoRgnOrder |= 4 << (i*4);
318                                         } else if (p[0] == 'E' && p[1] == 'U') {
319                                                 PicoIn.autoRgnOrder |= 8 << (i*4);
320                                         }
321                                         while (*p != ' ' && *p != 0) p++;
322                                         if (*p == 0) break;
323                                 }
324                         }
325                         else   if (strcasecmp(val, "Auto") == 0) {
326                                 PicoIn.regionOverride = 0;
327                         } else if (strcasecmp(val, "Japan NTSC") == 0) {
328                                 PicoIn.regionOverride = 1;
329                         } else if (strcasecmp(val, "Japan PAL") == 0) {
330                                 PicoIn.regionOverride = 2;
331                         } else if (strcasecmp(val, "USA") == 0) {
332                                 PicoIn.regionOverride = 4;
333                         } else if (strcasecmp(val, "Europe") == 0) {
334                                 PicoIn.regionOverride = 8;
335                         } else
336                                 return 0;
337                         return 1;
338
339                 case MA_32XOPT_MSH2_CYCLES:
340                         currentConfig.msh2_khz = atoi(val);
341                         Pico32xSetClocks(currentConfig.msh2_khz * 1000, 0);
342                         return 1;
343
344                 case MA_32XOPT_SSH2_CYCLES:
345                         currentConfig.ssh2_khz = atoi(val);
346                         Pico32xSetClocks(0, currentConfig.ssh2_khz * 1000);
347                         return 1;
348
349                 case MA_OPT2_GAMMA:
350                         currentConfig.gamma = atoi(val);
351                         return 1;
352
353                 case MA_OPT2_MAX_FRAMESKIP:
354                         currentConfig.max_skip = atoi(val);
355                         return 1;
356
357                 /* PSP */
358                 case MA_OPT3_VSYNC:
359                         // XXX: use enum
360                         if (strcasecmp(var, "Wait for vsync") != 0) return 0;
361                         if        (strcasecmp(val, "never") == 0) {
362                                 currentConfig.EmuOpt &= ~(EOPT_VSYNC|EOPT_VSYNC_MODE);
363                         } else if (strcasecmp(val, "sometimes") == 0) {
364                                 currentConfig.EmuOpt |=  (EOPT_VSYNC|EOPT_VSYNC_MODE);
365                         } else if (strcasecmp(val, "always") == 0) {
366                                 currentConfig.EmuOpt &= ~EOPT_VSYNC_MODE;
367                                 currentConfig.EmuOpt |=  EOPT_VSYNC;
368                         } else
369                                 return 0;
370                         return 1;
371
372                 default:
373                         lprintf("unhandled custom_read %i: %s\n", me->id, var);
374                         return 0;
375         }
376 }
377
378 static int parse_bind_val(const char *val, int *type)
379 {
380         int i;
381
382         *type = IN_BINDTYPE_NONE;
383         if (val[0] == 0)
384                 return 0;
385         
386         if (strncasecmp(val, "player", 6) == 0)
387         {
388                 int player, shift = 0;
389                 player = atoi(val + 6) - 1;
390
391                 if (player > 3)
392                         return -1;
393                 if (player & 1)
394                         shift = 16;
395
396                 *type = IN_BINDTYPE_PLAYER12 + (player >> 1);
397                 for (i = 0; me_ctrl_actions[i].name != NULL; i++) {
398                         if (strncasecmp(me_ctrl_actions[i].name, val + 8, strlen(val + 8)) == 0)
399                                 return me_ctrl_actions[i].mask << shift;
400                 }
401         }
402         for (i = 0; emuctrl_actions[i].name != NULL; i++) {
403                 if (strncasecmp(emuctrl_actions[i].name, val, strlen(val)) == 0) {
404                         *type = IN_BINDTYPE_EMU;
405                         return emuctrl_actions[i].mask;
406                 }
407         }
408
409         return -1;
410 }
411
412 static void keys_parse_all(FILE *f)
413 {
414         char line[640], *var, *val;
415         int dev_id = -1;
416         int acts, type;
417         int ret;
418
419         while (!feof(f))
420         {
421                 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
422                 if (ret ==  0) break;
423                 if (ret == -1) continue;
424
425                 if (strcasecmp(var, "binddev") == 0) {
426                         dev_id = in_config_parse_dev(val);
427                         if (dev_id < 0) {
428                                 printf("input: can't handle dev: %s\n", val);
429                                 continue;
430                         }
431                         in_unbind_all(dev_id, -1, -1);
432                         continue;
433                 }
434                 if (dev_id < 0 || strncasecmp(var, "bind ", 5) != 0)
435                         continue;
436
437                 acts = parse_bind_val(val, &type);
438                 if (acts == -1) {
439                         lprintf("config: unhandled action \"%s\"\n", val);
440                         continue;
441                 }
442
443                 mystrip(var + 5);
444                 in_config_bind_key(dev_id, var + 5, acts, type);
445         }
446         in_clean_binds();
447 }
448
449 static void parse(const char *var, const char *val, int *keys_encountered)
450 {
451         menu_entry *me;
452         int tmp;
453
454         if (strcasecmp(var, "LastUsedROM") == 0)
455                 return; /* handled elsewhere */
456
457         if (strncasecmp(var, "bind", 4) == 0) {
458                 *keys_encountered = 1;
459                 return; /* handled elsewhere */
460         }
461
462         if (strcasecmp(var, "Sound Volume") == 0) {
463                 currentConfig.volume = atoi(val);
464                 return;
465         }
466
467         for (me = me_list_get_first(); me != NULL; me = me_list_get_next())
468         {
469                 char *p;
470
471                 if (!me->need_to_save)
472                         continue;
473                 if (me->name == NULL || strcasecmp(var, me->name) != 0)
474                         continue;
475
476                 if (me->beh == MB_OPT_ONOFF) {
477                         tmp = strtol(val, &p, 0);
478                         if (*p != 0)
479                                 goto bad_val;
480                         if (tmp) *(int *)me->var |=  me->mask;
481                         else     *(int *)me->var &= ~me->mask;
482                         return;
483                 }
484                 else if (me->beh == MB_OPT_RANGE) {
485                         tmp = strtol(val, &p, 0);
486                         if (*p != 0)
487                                 goto bad_val;
488                         if (tmp < me->min) tmp = me->min;
489                         if (tmp > me->max) tmp = me->max;
490                         *(int *)me->var = tmp;
491                         return;
492                 }
493                 else if (me->beh == MB_OPT_ENUM) {
494                         const char **names, *p1;
495                         int i;
496
497                         names = (const char **)me->data;
498                         if (names == NULL)
499                                 goto bad_val;
500                         for (i = 0; names[i] != NULL; i++) {
501                                 for (p1 = names[i]; *p1 == ' '; p1++)
502                                         ;
503                                 if (strcasecmp(p1, val) == 0) {
504                                         *(int *)me->var = i;
505                                         return;
506                                 }
507                         }
508                         goto bad_val;
509                 }
510                 else if (custom_read(me, var, val))
511                         return;
512         }
513
514         lprintf("config_readsect: unhandled var: \"%s\"\n", var);
515         return;
516
517 bad_val:
518         lprintf("config_readsect: unhandled val for \"%s\": \"%s\"\n", var, val);
519 }
520
521 int config_readsect(const char *fname, const char *section)
522 {
523         char line[640], *var, *val;
524         int keys_encountered = 0;
525         FILE *f;
526         int ret;
527
528         f = fopen(fname, "r");
529         if (f == NULL) return -1;
530
531         if (section != NULL)
532         {
533                 /* for game_def.cfg only */
534                 ret = seek_sect(f, section);
535                 if (!ret) {
536                         lprintf("config_readsect: %s: missing section [%s]\n", fname, section);
537                         fclose(f);
538                         return -1;
539                 }
540         }
541
542         emu_set_defconfig();
543
544         while (!feof(f))
545         {
546                 ret = config_get_var_val(f, line, sizeof(line), &var, &val);
547                 if (ret ==  0) break;
548                 if (ret == -1) continue;
549
550                 parse(var, val, &keys_encountered);
551         }
552
553         if (keys_encountered) {
554                 rewind(f);
555                 keys_parse_all(f);
556         }
557
558         fclose(f);
559
560         lprintf("config_readsect: loaded from %s", fname);
561         if (section != NULL)
562                 lprintf(" [%s]", section);
563         printf("\n");
564
565         return 0;
566 }
567
568 #endif // _MSC_VER
569
570 static char *mystrip(char *str)
571 {
572         int i, len;
573
574         len = strlen(str);
575         for (i = 0; i < len; i++)
576                 if (str[i] != ' ') break;
577         if (i > 0) memmove(str, str + i, len - i + 1);
578
579         len = strlen(str);
580         for (i = len - 1; i >= 0; i--)
581                 if (str[i] != ' ') break;
582         str[i+1] = 0;
583
584         return str;
585 }
586
587 /* returns:
588  *  0 - EOF, end
589  *  1 - parsed ok
590  * -1 - failed to parse line
591  */
592 int config_get_var_val(void *file, char *line, int lsize, char **rvar, char **rval)
593 {
594         char *var, *val, *tmp;
595         FILE *f = file;
596         int len, i;
597
598         tmp = fgets(line, lsize, f);
599         if (tmp == NULL) return 0;
600
601         if (line[0] == '[') return 0; // other section
602
603         // strip comments, linefeed, spaces..
604         len = strlen(line);
605         for (i = 0; i < len; i++)
606                 if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; }
607         mystrip(line);
608         len = strlen(line);
609         if (len <= 0) return -1;;
610
611         // get var and val
612         for (i = 0; i < len; i++)
613                 if (line[i] == '=') break;
614         if (i >= len || strchr(&line[i+1], '=') != NULL) {
615                 lprintf("config_readsect: can't parse: %s\n", line);
616                 return -1;
617         }
618         line[i] = 0;
619         var = line;
620         val = &line[i+1];
621         mystrip(var);
622         mystrip(val);
623
624 #ifndef _MSC_VER
625         if (strlen(var) == 0 || (strlen(val) == 0 && strncasecmp(var, "bind", 4) != 0)) {
626                 lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val);
627                 return -1;;
628         }
629 #endif
630
631         *rvar = var;
632         *rval = val;
633         return 1;
634 }
635