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