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