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