quick save slots
[drnoksnes] / platform / config.cpp
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <popt.h>
5
6 #include "platform.h"
7 #include "port.h"
8 #include "snes9x.h"
9 #include "display.h"
10 #include "hgw.h"
11
12 #define DIE(format, ...) do { \
13                 fprintf(stderr, "Died at %s:%d: ", __FILE__, __LINE__ ); \
14                 fprintf(stderr, format "\n", ## __VA_ARGS__); \
15                 abort(); \
16         } while (0);
17
18 struct config Config;
19
20 /** Path to current rom file, with extension. */
21 static char * romFile;
22 /** Path to rom file, without extension.
23  *  Used as a simple optimization to S9xGetFilename
24  */
25 static char * basePath;
26
27 static struct poptOption commonOptionsTable[] = {
28         { "disable-audio", 'a', POPT_ARG_NONE, 0, 1,
29         "disable emulation and output of audio", 0 },
30         { "display-framerate", 'r', POPT_ARG_NONE, 0, 2,
31         "show frames per second counter in lower left corner", 0 },
32         { "skip-frames", 's', POPT_ARG_INT, 0, 3,
33         "render only 1 in every N frames", "NUM" },
34         { "fullscreen", 'f', POPT_ARG_NONE, 0, 4,
35         "start in fullscreen mode", 0 },
36         { "transparency", 'y', POPT_ARG_NONE, 0, 5,
37         "enable transparency effects (slower)", 0 },
38         { "hacks", 'h', POPT_ARG_STRING | POPT_ARGFLAG_OPTIONAL, 0, 6,
39         "enable hacks (yes, speed-only, no)", "option" },
40         { "pal", 'p', POPT_ARG_NONE, 0, 7,
41         "run in PAL mode", 0 },
42         { "ntsc", 'n', POPT_ARG_NONE, 0, 8,
43         "run in NTSC mode", 0 },
44         { "turbo", 't', POPT_ARG_NONE, 0, 9,
45         "turbo mode (do not try to sleep between frames)", 0 },
46         { "conf", 'c', POPT_ARG_STRING, 0, 10,
47         "extra configuration file to load", "FILE" },
48         { "mouse", 'm', POPT_ARG_INT | POPT_ARGFLAG_OPTIONAL, 0, 11,
49         "enable mouse on controller NUM", "NUM"},
50         { "superscope", 'e', POPT_ARG_NONE, 0, 12,
51         "enable SuperScope", 0},
52         { "snapshot", 'o', POPT_ARG_NONE, 0, 13,
53         "unfreeze previous game on start and freeze game on exit", 0 },
54         { "audio-rate", 'u', POPT_ARG_INT, 0, 14,
55         "audio output rate", "HZ" },
56         { "audio-buffer-size", 'b', POPT_ARG_INT, 0, 15,
57         "audio output buffer size", "SAMPLES" },
58         { "touchscreen", 'd', POPT_ARG_NONE, 0, 16,
59         "enable touchscreen controls", 0 },
60         POPT_TABLEEND
61 };
62
63 static struct poptOption configOptionsTable[] = {
64         { "scancode", '\0', POPT_ARG_INT, 0, 100,
65         "scancode to map", "CODE" },
66         { "button", '\0', POPT_ARG_STRING, 0, 101,
67         "SNES Button to press (A, B, X, Y, L, R, Up, Down, Left, Right)", "name" },
68         { "action", '\0', POPT_ARG_STRING, 0, 102,
69         "emulator action to do (fullscreen, quit, ...)", "action" },
70         { "hacks-file", '\0', POPT_ARG_STRING, 0, 200,
71         "path to snesadvance.dat file", "FILE" },
72         POPT_TABLEEND
73 };
74
75 static struct poptOption optionsTable[] = {
76         { 0, '\0', POPT_ARG_INCLUDE_TABLE, commonOptionsTable, 0,
77         "Common options", 0 },
78         { 0, '\0', POPT_ARG_INCLUDE_TABLE, configOptionsTable, 0,
79         "Configuration file options", 0 },
80         POPT_AUTOHELP
81         POPT_TABLEEND
82 };
83
84 static unsigned short buttonNameToBit(const char *s) {
85         if (strcasecmp(s, "A") == 0) {
86                 return SNES_A_MASK;
87         } else if (strcasecmp(s, "B") == 0) {
88                 return SNES_B_MASK;
89         } else if (strcasecmp(s, "X") == 0) {
90                 return SNES_X_MASK;
91         } else if (strcasecmp(s, "Y") == 0) {
92                 return SNES_Y_MASK;
93         } else if (strcasecmp(s, "L") == 0) {
94                 return SNES_TL_MASK;
95         } else if (strcasecmp(s, "R") == 0) {
96                 return SNES_TR_MASK;
97         } else if (strcasecmp(s, "UP") == 0) {
98                 return SNES_UP_MASK;
99         } else if (strcasecmp(s, "DOWN") == 0) {
100                 return SNES_DOWN_MASK;
101         } else if (strcasecmp(s, "LEFT") == 0) {
102                 return SNES_LEFT_MASK;
103         } else if (strcasecmp(s, "RIGHT") == 0) {
104                 return SNES_RIGHT_MASK;
105         } else if (strcasecmp(s, "START") == 0) {
106                 return SNES_START_MASK;
107         } else if (strcasecmp(s, "SELECT") == 0) {
108                 return SNES_SELECT_MASK;
109         } else {
110                 DIE("Bad button name: %s\n", s);
111         }
112 }
113
114 static unsigned char actionNameToBit(const char *s) {
115         if (strcasecmp(s, "quit") == 0) {
116                 return kActionQuit;
117         } else if (strcasecmp(s, "fullscreen") == 0) {
118                 return kActionToggleFullscreen;
119         } else if (strcasecmp(s, "quickload1") == 0) {
120                 return kActionQuickLoad1;
121         } else if (strcasecmp(s, "quicksave1") == 0) {
122                 return kActionQuickSave1;
123         } else if (strcasecmp(s, "quickload2") == 0) {
124                 return kActionQuickLoad2;
125         } else if (strcasecmp(s, "quicksave2") == 0) {
126                 return kActionQuickSave2;
127         } else {
128                 DIE("Bad action name: %s\n", s);
129         }
130 }
131
132 const char * S9xGetFilename(FileTypes file)
133 {
134         static char filename[PATH_MAX + 1];
135         const char * ext;
136         switch (file) {
137                 case FILE_ROM:
138                         return romFile;
139                 case FILE_SRAM:
140                         ext = "srm";
141                         break;
142                 case FILE_FREEZE:
143                         ext = "frz.gz";
144                         break;
145                 case FILE_CHT:
146                         ext = "cht";
147                         break;
148                 case FILE_IPS:
149                         ext = "ips";
150                         break;
151                 case FILE_SCREENSHOT:
152                         ext = "png";
153                         break;
154                 case FILE_SDD1_DAT:
155                         ext = "dat";
156                         break;
157                 default:
158                         ext = "???";
159                         break;
160         }
161
162         snprintf(filename, PATH_MAX, "%s.%s", basePath, ext);
163         return filename;
164 }
165
166 const char * S9xGetQuickSaveFilename(unsigned int slot)
167 {
168         static char filename[PATH_MAX + 1];
169         snprintf(filename, PATH_MAX, "%s.frz.%u.gz", basePath, slot);
170         return filename;
171 }
172
173 static void loadDefaults()
174 {
175         ZeroMemory(&Settings, sizeof(Settings));
176         ZeroMemory(&Config, sizeof(Config)); 
177         
178         romFile = 0;
179         basePath = 0;
180
181         Config.quitting = false;
182         Config.enableAudio = true;
183         Config.fullscreen = false;
184         Config.xsp = false;
185         Config.hacksFile = 0;
186         Config.touchscreenInput = false;
187
188         Settings.JoystickEnabled = FALSE;
189         Settings.SoundPlaybackRate = 22050;
190         Settings.Stereo = TRUE;
191         Settings.SoundBufferSize = 512; // in samples
192         Settings.CyclesPercentage = 100;
193         Settings.DisableSoundEcho = FALSE;
194         Settings.APUEnabled = FALSE;
195         Settings.H_Max = SNES_CYCLES_PER_SCANLINE;
196         Settings.SkipFrames = AUTO_FRAMERATE;
197         Settings.Shutdown = Settings.ShutdownMaster = TRUE;
198         Settings.FrameTimePAL = 20;     // in msecs
199         Settings.FrameTimeNTSC = 16;
200         Settings.FrameTime = Settings.FrameTimeNTSC;
201         Settings.DisableSampleCaching = FALSE;
202         Settings.DisableMasterVolume = FALSE;
203         Settings.Mouse = FALSE;
204         Settings.SuperScope = FALSE;
205         Settings.MultiPlayer5 = FALSE;
206         Settings.ControllerOption = SNES_JOYPAD;
207         
208         Settings.ForceTransparency = FALSE;
209         Settings.Transparency = FALSE;
210         Settings.SixteenBit = TRUE;
211         
212         Settings.SupportHiRes = FALSE;
213         Settings.NetPlay = FALSE;
214         Settings.ServerName [0] = 0;
215         Settings.AutoSaveDelay = 30;
216         Settings.ApplyCheats = FALSE;
217         Settings.TurboMode = FALSE;
218         Settings.TurboSkipFrames = 15;
219     
220     Settings.ForcePAL = FALSE;
221     Settings.ForceNTSC = FALSE;
222
223     Settings.HacksEnabled = FALSE;
224     Settings.HacksFilter = FALSE;
225
226         Settings.HBlankStart = (256 * Settings.H_Max) / SNES_HCOUNTER_MAX;
227
228         Settings.AutoSaveDelay = 15*60; // Autosave each 15 minutes.
229 }
230
231 void S9xSetRomFile(const char * path)
232 {
233         if (romFile) {
234                 free(romFile);
235                 free(basePath);
236         }
237
238         romFile = strndup(path, PATH_MAX);
239         basePath = strdup(romFile);
240
241         // Truncate base path at the last '.' char
242         char * c = strrchr(basePath, '.');
243         if (c) {
244                 if (strcasecmp(c, ".gz") == 0) {
245                         // Ignore the .gz part when truncating
246                         *c = '\0';
247                         c = strrchr(basePath, '.');
248                         if (c) {
249                                 *c = '\0';
250                         }
251                 } else {
252                         *c = '\0';
253                 }
254         }
255 }
256
257 static bool gotRomFile() 
258 {
259         return romFile ? true : false;
260 }
261
262 static void setHacks(const char * value)
263 {
264         // Unconditionally enable hacks even if no argument passed
265         Settings.HacksEnabled = TRUE;
266
267         if (!value) return;
268
269         if (strcasecmp(value, "speed-only") == 0 ||
270                 strcasecmp(value, "speed") == 0 ||
271                 strcasecmp(value, "s") == 0) {
272                         Settings.HacksFilter = TRUE;
273         } else if (strcasecmp(value, "yes") == 0 ||
274                 strcasecmp(value, "y") == 0) {
275                         // Do nothing
276         } else if (strcasecmp(value, "no") == 0 ||
277                 strcasecmp(value, "n") == 0) {
278                         Settings.HacksEnabled = FALSE;
279         } else {
280                 // Hack: the user probably wants to enable hacks
281                 // and use this argument as the ROM file.
282                 // Wonder why popt does not support this or if there's a better way.
283                 S9xSetRomFile(value);
284         }
285 }
286
287 static void loadConfig(poptContext optCon, const char * file)
288 {
289         char * out;
290         int newargc, ret;
291         const char ** newargv;
292         FILE * fp;
293
294         fp = fopen (file, "r");
295         if (!fp) {
296                 fprintf(stderr, "Cannot open config file %s\n", file);
297                 return;
298         }
299
300         ret = poptConfigFileToString (fp, &out, 0);
301         if (ret)
302             DIE("Cannot parse config file %s. ret=%d\n", file, ret);
303
304         poptParseArgvString(out, &newargc, &newargv);
305
306         poptStuffArgs(optCon, newargv);
307
308         free(out);
309         fclose(fp);
310         /* XXX: currently leaking newargv */
311 }
312
313 static void parseArgs(poptContext optCon)
314 {
315         int rc;
316         unsigned char scancode = 0;
317         
318         while ((rc = poptGetNextOpt(optCon)) > 0) {
319                 const char * val;
320                 switch (rc) {
321                         case 1:
322                                 Config.enableAudio = false;
323                                 break;
324                         case 2:
325                                 Settings.DisplayFrameRate = TRUE;
326                                 break;
327                         case 3:
328                                 Settings.SkipFrames = atoi(poptGetOptArg(optCon));
329                                 break;
330                         case 4:
331                                 Config.fullscreen = true;
332                                 break;
333                         case 5:
334                                 Settings.SixteenBit = TRUE;
335                                 Settings.Transparency = TRUE;
336                                 break;
337                         case 6:
338                                 Settings.HacksEnabled = TRUE;
339                                 setHacks(poptGetOptArg(optCon));
340                                 break;
341                         case 7:
342                                 Settings.ForcePAL = TRUE;
343                                 break;
344                         case 8:
345                                 Settings.ForceNTSC = TRUE;
346                                 break;
347                         case 9:
348                                 Settings.TurboMode = TRUE;
349                                 break;
350                         case 10:
351                                 loadConfig(optCon, poptGetOptArg(optCon));
352                                 break;
353                         case 11:
354                                 val = poptGetOptArg(optCon);
355                                 Settings.Mouse = TRUE;
356                                 if (!val || atoi(val) <= 1) {
357                                         // Enable mouse on first controller
358                                         Settings.ControllerOption = SNES_MOUSE_SWAPPED;
359                                 } else {
360                                         // Enable mouse on second controller
361                                         Settings.ControllerOption = SNES_MOUSE;
362                                 }
363                                 break;
364                         case 12:
365                                 Settings.SuperScope = TRUE;
366                                 Settings.ControllerOption = SNES_SUPERSCOPE;
367                                 break;
368                         case 13:
369                                 Config.snapshotLoad = true;
370                                 Config.snapshotSave = true;
371                                 break;
372                         case 14:
373                                 Settings.SoundPlaybackRate = atoi(poptGetOptArg(optCon));
374                                 break;
375                         case 15:
376                                 Settings.SoundBufferSize = atoi(poptGetOptArg(optCon));
377                                 break;
378                         case 16:
379                                 Config.touchscreenInput = true;
380                                 break;
381                         case 100:
382                                 scancode = atoi(poptGetOptArg(optCon));
383                                 break;
384                         case 101:
385                                 Config.joypad1Mapping[scancode] |= 
386                                         buttonNameToBit(poptGetOptArg(optCon));
387                                 break;
388                         case 102:
389                                 Config.action[scancode] |= 
390                                         actionNameToBit(poptGetOptArg(optCon));
391                                 break;
392                         case 200:
393                                 free(Config.hacksFile);
394                                 Config.hacksFile = strdup(poptGetOptArg(optCon));
395                                 break;
396                         default:
397                                 DIE("Invalid popt argument (this is a bug): %d", rc);
398                                 break;
399                 }
400         }
401         
402         if (rc < -1) {
403                 /* an error occurred during option processing */
404                 fprintf(stderr, "%s: %s\n",
405                         poptBadOption(optCon, 0),
406                         poptStrerror(rc));
407                 exit(2);
408         }
409
410         /* if there's an extra unparsed arg it's our rom file */
411         const char * extra_arg = poptGetArg(optCon);
412         if (extra_arg) 
413                 S9xSetRomFile(extra_arg);
414 }
415
416 void S9xLoadConfig(int argc, const char ** argv)
417 {
418         poptContext optCon =
419                 poptGetContext("drnoksnes", argc, argv, optionsTable, 0);
420         poptSetOtherOptionHelp(optCon, "<rom>");
421
422         // Builtin defaults
423         loadDefaults();
424
425         // Read config file ~/apps/DrNokSnes.txt
426         char defConfFile[PATH_MAX];
427         sprintf(defConfFile, "%s/%s", getenv("HOME"), "apps/DrNokSnes.txt");
428         loadConfig(optCon, defConfFile);
429
430         // Command line parameters (including --conf args)
431         parseArgs(optCon);
432
433         if (!gotRomFile() && !hgwLaunched) {
434                 // User did not specify a ROM file, 
435                 // and we're not being launched from D-Bus.
436                 fprintf(stderr, "You need to specify a ROM, like this:\n");
437                 poptPrintUsage(optCon, stdout, 0);
438                 poptFreeContext(optCon); 
439                 exit(2);
440         }
441
442         poptFreeContext(optCon);
443 }
444
445 void S9xUnloadConfig()
446 {
447         if (romFile) {
448                 free(romFile);
449                 romFile = 0;
450         }
451         if (basePath) {
452                 free(basePath);
453                 basePath = 0;
454         }
455         if (Config.hacksFile) {
456                 free(Config.hacksFile);
457                 Config.hacksFile = 0;
458         }
459 }
460