c51bd2fe |
1 | /* |
2 | * Human-readable config file management for PicoDrive |
3 | * (c) |
4 | */ |
5 | |
6 | #include <string.h> |
7 | #include <stdlib.h> |
8 | #include "config.h" |
9 | #include "menu.h" |
10 | #include "emu.h" |
3e85ebdd |
11 | #include "lprintf.h" |
c51bd2fe |
12 | #include <Pico/Pico.h> |
13 | |
14 | extern menu_entry opt_entries[]; |
15 | extern menu_entry opt2_entries[]; |
16 | extern menu_entry cdopt_entries[]; |
17 | extern const int opt_entry_count; |
18 | extern const int opt2_entry_count; |
19 | extern const int cdopt_entry_count; |
20 | |
21 | static menu_entry *cfg_opts[] = { opt_entries, opt2_entries, cdopt_entries }; |
22 | static const int *cfg_opt_counts[] = { &opt_entry_count, &opt2_entry_count, &cdopt_entry_count }; |
23 | |
24 | #define NL "\n" |
25 | |
26 | |
27 | static int seek_sect(FILE *f, const char *section) |
28 | { |
29 | char line[128], *tmp; |
30 | int len; |
31 | |
32 | len = strlen(section); |
33 | // seek to the section needed |
34 | while (!feof(f)) |
35 | { |
36 | tmp = fgets(line, sizeof(line), f); |
37 | if (tmp == NULL) break; |
38 | |
39 | if (line[0] != '[') continue; // not section start |
40 | if (strncmp(line + 1, section, len) == 0 && line[len+1] == ']') |
41 | return 1; // found it |
42 | } |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | |
3e85ebdd |
48 | static void custom_write(FILE *f, const menu_entry *me, int no_def) |
c51bd2fe |
49 | { |
50 | char *str, str24[24]; |
51 | |
52 | switch (me->id) |
53 | { |
54 | case MA_OPT_RENDERER: |
3e85ebdd |
55 | if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&0x10) && |
c51bd2fe |
56 | !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return; |
57 | if (PicoOpt&0x10) |
58 | str = "8bit fast"; |
59 | else if (currentConfig.EmuOpt&0x80) |
60 | str = "16bit accurate"; |
61 | else |
62 | str = "8bit accurate"; |
63 | fprintf(f, "Renderer = %s", str); |
64 | break; |
65 | |
66 | case MA_OPT_SCALING: |
3e85ebdd |
67 | if (no_def && defaultConfig.scaling == currentConfig.scaling) return; |
c51bd2fe |
68 | switch (currentConfig.scaling) { |
69 | default: str = "OFF"; break; |
70 | case 1: str = "hw horizontal"; break; |
71 | case 2: str = "hw horiz. + vert."; break; |
72 | case 3: str = "sw horizontal"; break; |
73 | } |
74 | fprintf(f, "Scaling = %s", str); |
75 | break; |
76 | case MA_OPT_FRAMESKIP: |
3e85ebdd |
77 | if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return; |
c51bd2fe |
78 | if (currentConfig.Frameskip < 0) |
79 | strcpy(str24, "Auto"); |
80 | else sprintf(str24, "%i", currentConfig.Frameskip); |
81 | fprintf(f, "Frameskip = %s", str24); |
82 | break; |
83 | case MA_OPT_SOUND_QUALITY: |
3e85ebdd |
84 | if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&8) && |
c51bd2fe |
85 | defaultConfig.s_PsndRate == PsndRate) return; |
86 | str = (PicoOpt&0x08)?"stereo":"mono"; |
87 | fprintf(f, "Sound Quality = %i %s", PsndRate, str); |
88 | break; |
89 | case MA_OPT_REGION: |
3e85ebdd |
90 | if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride && |
c51bd2fe |
91 | defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return; |
92 | fprintf(f, "Region = %s", me_region_name(PicoRegionOverride, PicoAutoRgnOrder)); |
93 | break; |
94 | case MA_OPT_CONFIRM_STATES: |
3e85ebdd |
95 | if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return; |
c51bd2fe |
96 | switch ((currentConfig.EmuOpt >> 9) & 5) { |
97 | default: str = "OFF"; break; |
98 | case 1: str = "writes"; break; |
99 | case 4: str = "loads"; break; |
100 | case 5: str = "both"; break; |
101 | } |
102 | fprintf(f, "Confirm savestate = %s", str); |
103 | break; |
104 | case MA_OPT_CPU_CLOCKS: |
3e85ebdd |
105 | if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return; |
c51bd2fe |
106 | fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock); |
107 | break; |
108 | case MA_OPT2_GAMMA: |
3e85ebdd |
109 | if (no_def && defaultConfig.gamma == currentConfig.gamma) return; |
c51bd2fe |
110 | fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0); |
111 | break; |
112 | case MA_OPT2_SQUIDGEHACK: |
3e85ebdd |
113 | if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x0010)) return; |
c51bd2fe |
114 | fprintf(f, "Squidgehack = %i", (currentConfig.EmuOpt&0x0010)>>4); |
115 | break; |
116 | case MA_CDOPT_READAHEAD: |
3e85ebdd |
117 | if (no_def && defaultConfig.s_PicoCDBuffers == PicoCDBuffers) return; |
c51bd2fe |
118 | sprintf(str24, "%i", PicoCDBuffers * 2); |
119 | fprintf(f, "ReadAhead buffer = %s", str24); |
120 | break; |
121 | |
122 | default: |
3e85ebdd |
123 | lprintf("unhandled custom_write: %i\n", me->id); |
c51bd2fe |
124 | return; |
125 | } |
126 | fprintf(f, NL); |
127 | } |
128 | |
3e85ebdd |
129 | static int default_var(const menu_entry *me) |
130 | { |
131 | switch (me->id) |
132 | { |
133 | case MA_OPT_ACC_TIMING: |
134 | case MA_OPT_ACC_SPRITES: |
135 | case MA_OPT_ARM940_SOUND: |
136 | case MA_OPT_6BUTTON_PAD: |
137 | case MA_OPT2_ENABLE_Z80: |
138 | case MA_OPT2_ENABLE_YM2612: |
139 | case MA_OPT2_ENABLE_SN76496: |
140 | case MA_CDOPT_CDDA: |
141 | case MA_CDOPT_PCM: |
142 | case MA_CDOPT_SAVERAM: |
143 | case MA_CDOPT_SCALEROT_CHIP: |
144 | case MA_CDOPT_BETTER_SYNC: |
145 | return defaultConfig.s_PicoOpt; |
146 | |
147 | case MA_OPT_SHOW_FPS: |
148 | case MA_OPT_ENABLE_SOUND: |
149 | case MA_OPT_SRAM_STATES: |
150 | case MA_OPT2_A_SN_GAMMA: |
151 | case MA_OPT2_VSYNC: |
152 | case MA_OPT2_GZIP_STATES: |
153 | case MA_OPT2_NO_LAST_ROM: |
154 | case MA_OPT2_RAMTIMINGS: |
155 | case MA_CDOPT_LEDS: |
156 | return defaultConfig.EmuOpt; |
157 | |
158 | case MA_OPT_SAVE_SLOT: |
159 | default: |
160 | return 0; |
161 | } |
162 | } |
c51bd2fe |
163 | |
164 | int config_writesect(const char *fname, const char *section) |
165 | { |
166 | FILE *fo = NULL, *fn = NULL; // old and new |
3e85ebdd |
167 | int no_defaults = 0; // avoid saving defaults |
c51bd2fe |
168 | menu_entry *me; |
169 | int t, i, tlen, ret; |
170 | char line[128], *tmp; |
171 | |
172 | if (section != NULL) |
173 | { |
3e85ebdd |
174 | no_defaults = 1; |
175 | |
c51bd2fe |
176 | fo = fopen(fname, "r"); |
177 | if (fo == NULL) { |
178 | fn = fopen(fname, "w"); |
179 | goto write; |
180 | } |
181 | |
182 | ret = seek_sect(fo, section); |
183 | if (!ret) { |
184 | // sect not found, we can simply append |
185 | fclose(fo); fo = NULL; |
186 | fn = fopen(fname, "a"); |
187 | goto write; |
188 | } |
189 | |
190 | // use 2 files.. |
191 | fclose(fo); |
192 | rename(fname, "tmp.cfg"); |
193 | fo = fopen("tmp.cfg", "r"); |
194 | fn = fopen(fname, "w"); |
195 | if (fo == NULL || fn == NULL) goto write; |
196 | |
197 | // copy everything until sect |
198 | tlen = strlen(section); |
199 | while (!feof(fo)) |
200 | { |
201 | tmp = fgets(line, sizeof(line), fo); |
202 | if (tmp == NULL) break; |
203 | |
204 | if (line[0] == '[' && strncmp(line + 1, section, tlen) == 0 && line[tlen+1] == ']') |
205 | break; |
206 | fputs(line, fn); |
207 | } |
208 | |
209 | // now skip to next sect |
210 | while (!feof(fo)) |
211 | { |
212 | tmp = fgets(line, sizeof(line), fo); |
213 | if (tmp == NULL) break; |
214 | if (line[0] == '[') { |
215 | fseek(fo, -strlen(line), SEEK_CUR); |
216 | break; |
217 | } |
218 | } |
219 | if (feof(fo)) |
220 | { |
221 | fclose(fo); fo = NULL; |
222 | remove("tmp.cfg"); |
223 | } |
224 | } |
225 | else |
226 | { |
227 | fn = fopen(fname, "w"); |
228 | } |
229 | |
230 | write: |
231 | if (fn == NULL) { |
232 | if (fo) fclose(fo); |
233 | return -1; |
234 | } |
235 | if (section != NULL) |
236 | fprintf(fn, "[%s]" NL, section); |
237 | |
238 | for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++) |
239 | { |
240 | me = cfg_opts[t]; |
241 | tlen = *(cfg_opt_counts[t]); |
242 | for (i = 0; i < tlen; i++, me++) |
243 | { |
244 | if (!me->need_to_save) continue; |
245 | if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL) |
3e85ebdd |
246 | custom_write(fn, me, no_defaults); |
247 | else if (me->beh == MB_ONOFF) { |
248 | if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask)) |
249 | fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0); |
250 | } else if (me->beh == MB_RANGE) { |
251 | if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask)) |
252 | fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var); |
253 | } |
c51bd2fe |
254 | } |
255 | } |
256 | fprintf(fn, NL); |
257 | |
258 | if (fo != NULL) |
259 | { |
260 | // copy whatever is left |
261 | while (!feof(fo)) |
262 | { |
263 | tmp = fgets(line, sizeof(line), fo); |
264 | if (tmp == NULL) break; |
265 | |
266 | fputs(line, fn); |
267 | } |
268 | fclose(fo); |
269 | remove("tmp.cfg"); |
270 | } |
271 | |
272 | fclose(fn); |
273 | return 0; |
274 | } |
275 | |
276 | |
277 | static void mystrip(char *str) |
278 | { |
279 | int i, len; |
280 | |
281 | len = strlen(str); |
282 | for (i = 0; i < len; i++) |
283 | if (str[i] != ' ') break; |
284 | if (i > 0) memmove(str, str + i, len - i + 1); |
285 | len = strlen(str); |
286 | for (i = len - 1; i >= 0; i--) |
287 | if (str[i] != ' ') break; |
288 | str[i+1] = 0; |
289 | } |
290 | |
291 | |
292 | int config_writelrom(const char *fname) |
293 | { |
294 | char line[128], *tmp, *optr = NULL; |
295 | char *old_data = NULL; |
296 | int size; |
297 | FILE *f; |
298 | |
299 | if (strlen(currentConfig.lastRomFile) == 0) return 0; |
300 | |
301 | f = fopen(fname, "r"); |
302 | if (f != NULL) |
303 | { |
304 | fseek(f, 0, SEEK_END); |
305 | size = ftell(f); |
306 | fseek(f, 0, SEEK_SET); |
307 | old_data = malloc(size + size/8); |
308 | if (old_data != NULL) |
309 | { |
310 | optr = old_data; |
311 | while (!feof(f)) |
312 | { |
313 | tmp = fgets(line, sizeof(line), f); |
314 | if (tmp == NULL) break; |
315 | mystrip(line); |
316 | if (strncasecmp(line, "LastUsedROM", 11) == 0) |
317 | continue; |
318 | sprintf(optr, "%s", line); |
319 | optr += strlen(optr); |
320 | } |
321 | } |
322 | fclose(f); |
323 | } |
324 | |
325 | f = fopen(fname, "w"); |
326 | if (f == NULL) return -1; |
327 | |
328 | if (old_data != NULL) { |
329 | fwrite(old_data, 1, optr - old_data, f); |
330 | free(old_data); |
331 | } |
332 | fprintf(f, "LastUsedROM = %s" NL, currentConfig.lastRomFile); |
333 | fclose(f); |
334 | return 0; |
335 | } |
336 | |
337 | |
338 | static int custom_read(menu_entry *me, const char *var, const char *val) |
339 | { |
340 | char *tmp; |
341 | int tmpi; |
342 | |
343 | switch (me->id) |
344 | { |
345 | case MA_OPT_RENDERER: |
346 | if (strcasecmp(var, "Renderer") != 0) return 0; |
347 | if (strcasecmp(val, "8bit fast") == 0) { |
348 | PicoOpt |= 0x10; |
349 | } |
350 | else if (strcasecmp(val, "16bit accurate") == 0) { |
351 | PicoOpt &= ~0x10; |
352 | currentConfig.EmuOpt |= 0x80; |
353 | } |
354 | else if (strcasecmp(val, "8bit accurate") == 0) { |
355 | PicoOpt &= ~0x10; |
356 | currentConfig.EmuOpt &= ~0x80; |
357 | } |
358 | else |
359 | return 0; |
360 | return 1; |
361 | |
362 | case MA_OPT_SCALING: |
363 | if (strcasecmp(var, "Scaling") != 0) return 0; |
364 | if (strcasecmp(val, "OFF") == 0) { |
365 | currentConfig.scaling = 0; |
366 | } else if (strcasecmp(val, "hw horizontal") == 0) { |
367 | currentConfig.scaling = 1; |
368 | } else if (strcasecmp(val, "hw horiz. + vert.") == 0) { |
369 | currentConfig.scaling = 2; |
370 | } else if (strcasecmp(val, "sw horizontal") == 0) { |
371 | currentConfig.scaling = 3; |
372 | } else |
373 | return 0; |
374 | return 1; |
375 | |
376 | case MA_OPT_FRAMESKIP: |
377 | if (strcasecmp(var, "Frameskip") != 0) return 0; |
378 | if (strcasecmp(val, "Auto") == 0) |
379 | currentConfig.Frameskip = -1; |
380 | else currentConfig.Frameskip = atoi(val); |
381 | return 1; |
382 | |
383 | case MA_OPT_SOUND_QUALITY: |
384 | if (strcasecmp(var, "Sound Quality") != 0) return 0; |
385 | PsndRate = strtoul(val, &tmp, 10); |
386 | if (PsndRate < 8000 || PsndRate > 44100) |
387 | PsndRate = 22050; |
388 | while (*tmp == ' ') tmp++; |
389 | if (strcasecmp(tmp, "stereo") == 0) { |
390 | PicoOpt |= 8; |
391 | } else if (strcasecmp(tmp, "mono") == 0) { |
392 | PicoOpt &= ~8; |
393 | } else |
394 | return 0; |
395 | return 1; |
396 | |
397 | case MA_OPT_REGION: |
398 | if (strcasecmp(var, "Region") != 0) return 0; |
399 | if (strncasecmp(val, "Auto: ", 6) == 0) |
400 | { |
401 | const char *p = val + 5, *end = val + strlen(val); |
402 | int i; |
403 | PicoRegionOverride = PicoAutoRgnOrder = 0; |
3e85ebdd |
404 | for (i = 0; p < end && i < 3; i++) |
405 | { |
406 | while (*p == ' ') p++; |
c51bd2fe |
407 | if (p[0] == 'J' && p[1] == 'P') { |
408 | PicoAutoRgnOrder |= 1 << (i*4); |
409 | } else if (p[0] == 'U' && p[1] == 'S') { |
410 | PicoAutoRgnOrder |= 4 << (i*4); |
411 | } else if (p[0] == 'E' && p[1] == 'U') { |
412 | PicoAutoRgnOrder |= 8 << (i*4); |
413 | } |
3e85ebdd |
414 | while (*p != ' ' && *p != 0) p++; |
415 | if (*p == 0) break; |
c51bd2fe |
416 | } |
417 | } |
418 | else if (strcasecmp(val, "Auto") == 0) { |
419 | PicoRegionOverride = 0; |
420 | } else if (strcasecmp(val, "Japan NTSC") == 0) { |
421 | PicoRegionOverride = 1; |
422 | } else if (strcasecmp(val, "Japan PAL") == 0) { |
423 | PicoRegionOverride = 2; |
424 | } else if (strcasecmp(val, "USA") == 0) { |
425 | PicoRegionOverride = 4; |
426 | } else if (strcasecmp(val, "Europe") == 0) { |
427 | PicoRegionOverride = 8; |
428 | } else |
429 | return 0; |
430 | return 1; |
431 | |
432 | case MA_OPT_CONFIRM_STATES: |
433 | if (strcasecmp(var, "Confirm savestate") != 0) return 0; |
434 | if (strcasecmp(val, "OFF") == 0) { |
435 | currentConfig.EmuOpt &= 5<<9; |
436 | } else if (strcasecmp(val, "writes") == 0) { |
437 | currentConfig.EmuOpt &= 5<<9; |
438 | currentConfig.EmuOpt |= 1<<9; |
439 | } else if (strcasecmp(val, "loads") == 0) { |
440 | currentConfig.EmuOpt &= 5<<9; |
441 | currentConfig.EmuOpt |= 4<<9; |
442 | } else if (strcasecmp(val, "both") == 0) { |
443 | currentConfig.EmuOpt &= 5<<9; |
444 | currentConfig.EmuOpt |= 5<<9; |
445 | } else |
446 | return 0; |
447 | return 1; |
448 | |
449 | case MA_OPT_CPU_CLOCKS: |
450 | if (strcasecmp(var, "GP2X CPU clocks") != 0) return 0; |
451 | currentConfig.CPUclock = atoi(val); |
452 | return 1; |
453 | |
454 | case MA_OPT2_GAMMA: |
455 | if (strcasecmp(var, "Gamma correction") != 0) return 0; |
456 | currentConfig.gamma = (int) (atof(val) * 100.0); |
457 | return 1; |
458 | |
459 | case MA_OPT2_SQUIDGEHACK: |
460 | if (strcasecmp(var, "Squidgehack") != 0) return 0; |
461 | tmpi = atoi(val); |
462 | if (tmpi) *(int *)me->var |= me->mask; |
463 | else *(int *)me->var &= ~me->mask; |
464 | return 1; |
465 | |
466 | case MA_CDOPT_READAHEAD: |
467 | if (strcasecmp(var, "ReadAhead buffer") != 0) return 0; |
468 | PicoCDBuffers = atoi(val) / 2; |
469 | return 1; |
470 | |
471 | default: |
472 | if (strcasecmp(var, "LastUsedROM") == 0) { |
473 | tmpi = sizeof(currentConfig.lastRomFile); |
474 | strncpy(currentConfig.lastRomFile, val, tmpi); |
475 | currentConfig.lastRomFile[tmpi-1] = 0; |
476 | return 1; |
477 | } |
3e85ebdd |
478 | lprintf("unhandled custom_read: %i\n", me->id); |
c51bd2fe |
479 | return 0; |
480 | } |
481 | } |
482 | |
483 | |
484 | static void parse(const char *var, const char *val) |
485 | { |
486 | menu_entry *me; |
487 | int t, i, tlen, tmp, ret = 0; |
488 | |
489 | for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++) |
490 | { |
491 | me = cfg_opts[t]; |
492 | tlen = *(cfg_opt_counts[t]); |
493 | for (i = 0; i < tlen && ret == 0; i++, me++) |
494 | { |
495 | if (!me->need_to_save) continue; |
496 | if (me->name != NULL) { |
497 | if (strcasecmp(var, me->name) != 0) continue; // surely not this one |
498 | if (me->beh == MB_ONOFF) { |
499 | tmp = atoi(val); |
500 | if (tmp) *(int *)me->var |= me->mask; |
501 | else *(int *)me->var &= ~me->mask; |
502 | return; |
503 | } else if (me->beh == MB_RANGE) { |
504 | tmp = atoi(val); |
505 | if (tmp < me->min) tmp = me->min; |
506 | if (tmp > me->max) tmp = me->max; |
507 | *(int *)me->var = tmp; |
508 | return; |
509 | } |
510 | } |
511 | ret = custom_read(me, var, val); |
512 | } |
513 | } |
3e85ebdd |
514 | if (!ret) lprintf("config_readsect: unhandled var: %s\n", var); |
c51bd2fe |
515 | } |
516 | |
517 | |
518 | int config_readsect(const char *fname, const char *section) |
519 | { |
520 | char line[128], *var, *val, *tmp; |
521 | FILE *f = fopen(fname, "r"); |
522 | int len, i, ret; |
523 | |
524 | if (f == NULL) return 0; |
525 | |
526 | if (section != NULL) |
527 | { |
528 | ret = seek_sect(f, section); |
529 | if (!ret) { |
3e85ebdd |
530 | lprintf("config_readsect: %s: missing section [%s]\n", fname, section); |
c51bd2fe |
531 | fclose(f); |
532 | return -1; |
533 | } |
534 | } |
535 | |
536 | while (!feof(f)) |
537 | { |
538 | tmp = fgets(line, sizeof(line), f); |
539 | if (tmp == NULL) break; |
540 | |
541 | if (line[0] == '[') break; // other section |
542 | |
543 | // strip comments, linefeed, spaces.. |
544 | len = strlen(line); |
545 | for (i = 0; i < len; i++) |
546 | if (line[i] == '#' || line[i] == '\r' || line[i] == '\n') { line[i] = 0; break; } |
547 | mystrip(line); |
548 | len = strlen(line); |
549 | if (len <= 0) continue; |
550 | |
551 | // get var and val |
552 | for (i = 0; i < len; i++) |
553 | if (line[i] == '=') break; |
554 | if (i >= len || strchr(&line[i+1], '=') != NULL) { |
3e85ebdd |
555 | lprintf("config_readsect: can't parse: %s\n", line); |
c51bd2fe |
556 | continue; |
557 | } |
558 | line[i] = 0; |
559 | var = line; |
560 | val = &line[i+1]; |
561 | mystrip(var); |
562 | mystrip(val); |
563 | if (strlen(var) == 0 || strlen(val) == 0) { |
3e85ebdd |
564 | lprintf("config_readsect: something's empty: \"%s\" = \"%s\"\n", var, val); |
c51bd2fe |
565 | continue; |
566 | } |
567 | |
568 | parse(var, val); |
569 | } |
570 | |
571 | fclose(f); |
572 | return 0; |
573 | } |
574 | |