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