extra input methods
[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 static const struct poptOption optionsTable[] = {
21         { "disable-audio", 'a', POPT_ARG_NONE, 0, 1,
22         "disable emulation and output of audio", 0 },
23         { "display-framerate", 'r', POPT_ARG_NONE, 0, 2,
24         "Show frames per second counter in lower left corner", 0 },
25         { "skip-frames", 's', POPT_ARG_INT, 0, 3,
26         "Render only 1 in every N frames", "NUM" },
27         { "fullscreen", 'f', POPT_ARG_NONE, 0, 4,
28         "Start in fullscreen mode", 0 },
29         { "transparency", 'y', POPT_ARG_NONE, 0, 5,
30         "Enable transparency effects (slower)", 0 },
31         { "hacks", 'h', POPT_ARG_STRING | POPT_ARGFLAG_OPTIONAL, 0, 6,
32         "Enable hacks (yes, speed-only, no)", "option" },
33         { "pal", 'p', POPT_ARG_NONE, 0, 7,
34         "Run in PAL mode", 0 },
35         { "ntsc", 'n', POPT_ARG_NONE, 0, 8,
36         "Run in NTSC mode", 0 },
37         { "turbo", 't', POPT_ARG_NONE, 0, 9,
38         "Turbo mode (do not try to sleep between frames)", 0 },
39         { "conf", 'c', POPT_ARG_STRING, 0, 10,
40         "Extra configuration file to load", "FILE" },
41         { "mouse", 'm', POPT_ARG_INT | POPT_ARGFLAG_OPTIONAL, 0, 11,
42         "Enable mouse on controller NUM", "NUM"},
43         { "superscope", 'e', POPT_ARG_NONE, 0, 12,
44         "Enable SuperScope", 0},
45         { "scancode", '\0', POPT_ARG_INT, 0, 100,
46         "Scancode to map", "CODE" },
47         { "button", '\0', POPT_ARG_STRING, 0, 101,
48         "SNES Button to press (A, B, X, Y, L, R, Up, Down, Left, Right)", "name" },
49         { "action", '\0', POPT_ARG_STRING, 0, 102,
50         "Emulator action to do (fullscreen, quit, ...)", "action" },
51         { "hacks-file", '\0', POPT_ARG_STRING, 0, 200,
52         "Path to snesadvance.dat file", "FILE" },
53         POPT_AUTOHELP
54         POPT_TABLEEND
55 };
56
57 static unsigned short buttonNameToBit(const char *s) {
58         if (strcasecmp(s, "A") == 0) {
59                 return SNES_A_MASK;
60         } else if (strcasecmp(s, "B") == 0) {
61                 return SNES_B_MASK;
62         } else if (strcasecmp(s, "X") == 0) {
63                 return SNES_X_MASK;
64         } else if (strcasecmp(s, "Y") == 0) {
65                 return SNES_Y_MASK;
66         } else if (strcasecmp(s, "L") == 0) {
67                 return SNES_TL_MASK;
68         } else if (strcasecmp(s, "R") == 0) {
69                 return SNES_TR_MASK;
70         } else if (strcasecmp(s, "UP") == 0) {
71                 return SNES_UP_MASK;
72         } else if (strcasecmp(s, "DOWN") == 0) {
73                 return SNES_DOWN_MASK;
74         } else if (strcasecmp(s, "LEFT") == 0) {
75                 return SNES_LEFT_MASK;
76         } else if (strcasecmp(s, "RIGHT") == 0) {
77                 return SNES_RIGHT_MASK;
78         } else if (strcasecmp(s, "START") == 0) {
79                 return SNES_START_MASK;
80         } else if (strcasecmp(s, "SELECT") == 0) {
81                 return SNES_SELECT_MASK;
82         } else {
83                 DIE("Bad button name: %s\n", s);
84         }
85 }
86
87 static unsigned char actionNameToBit(const char *s) {
88         if (strcasecmp(s, "quit") == 0) {
89                 return kActionQuit;
90         } else if (strcasecmp(s, "fullscreen") == 0) {
91                 return kActionToggleFullscreen;
92         } else {
93                 DIE("Bad action name: %s\n", s);
94         }
95 }
96
97 const char * S9xFiletitle(const char * f)
98 {
99         if (!f) return 0;
100         
101         const char * p = strrchr (f, '.');
102
103         if (p)
104                 return p + 1;
105
106         return f;
107 }
108
109 const char * S9xBasename(const char * f)
110 {
111         const char * p = strrchr (f, '/');
112
113         if (p)
114                 return p + 1;
115
116         return f;
117 }
118
119 const char * S9xGetFilename(const char * ext)
120 {
121         static char filename [PATH_MAX + 1];
122         sprintf(filename, "%s%s", Config.romFile, ext);
123
124         return filename;
125 }
126
127 static void loadDefaults()
128 {
129         ZeroMemory(&Settings, sizeof(Settings));
130         ZeroMemory(&Config, sizeof(Config)); 
131         
132         Config.quitting = false;
133         Config.enableAudio = true;
134         Config.fullscreen = false;
135         Config.xsp = false;
136
137         Settings.JoystickEnabled = FALSE;
138         Settings.SoundPlaybackRate = 22050;
139         Settings.Stereo = TRUE;
140         Settings.SoundBufferSize = 0;
141         Settings.CyclesPercentage = 100;
142         Settings.DisableSoundEcho = FALSE;
143         Settings.APUEnabled = FALSE;
144         Settings.H_Max = SNES_CYCLES_PER_SCANLINE;
145         Settings.SkipFrames = AUTO_FRAMERATE;
146         Settings.Shutdown = Settings.ShutdownMaster = TRUE;
147         Settings.FrameTimePAL = 20000;  // in usecs
148         Settings.FrameTimeNTSC = 16667;
149         Settings.FrameTime = Settings.FrameTimeNTSC;
150         Settings.DisableSampleCaching = FALSE;
151         Settings.DisableMasterVolume = FALSE;
152         Settings.Mouse = FALSE;
153         Settings.SuperScope = FALSE;
154         Settings.MultiPlayer5 = FALSE;
155         Settings.ControllerOption = SNES_JOYPAD;
156         
157         Settings.ForceTransparency = FALSE;
158         Settings.Transparency = FALSE;
159         Settings.SixteenBit = TRUE;
160         
161         Settings.SupportHiRes = FALSE;
162         Settings.NetPlay = FALSE;
163         Settings.ServerName [0] = 0;
164         Settings.AutoSaveDelay = 30;
165         Settings.ApplyCheats = FALSE;
166         Settings.TurboMode = FALSE;
167         Settings.TurboSkipFrames = 15;
168         //Settings.ThreadSound = FALSE;
169         //Settings.SoundSync = FALSE;
170         //Settings.NoPatch = true;
171     
172     Settings.ForcePAL = FALSE;
173     Settings.ForceNTSC = FALSE;
174
175     Settings.HacksEnabled = FALSE;
176     Settings.HacksFilter = FALSE;
177
178         Settings.HBlankStart = (256 * Settings.H_Max) / SNES_HCOUNTER_MAX;
179
180         Settings.AutoSaveDelay = 15*60; // Autosave each 15 minutes.
181 }
182
183 void S9xSetRomFile(const char * path)
184 {
185         char drive[1], dir[PATH_MAX], fname[PATH_MAX], ext[PATH_MAX];
186         
187         _splitpath (path, drive, dir, fname, ext);
188         sprintf(Config.romFile, "%s%s%s", dir, strlen(dir) > 0 ? "/" : "", fname);
189 }
190
191 static bool gotRomFile() 
192 {
193         return Config.romFile[0] != '\0';
194 }
195
196 static void setHacks(const char * value)
197 {
198         // Unconditionally enable hacks even if no argument passed
199         Settings.HacksEnabled = TRUE;
200
201         if (!value) return;
202
203         if (strcasecmp(value, "speed-only") == 0 ||
204                 strcasecmp(value, "speed") == 0 ||
205                 strcasecmp(value, "s") == 0) {
206                         Settings.HacksFilter = TRUE;
207         } else if (strcasecmp(value, "yes") == 0 ||
208                 strcasecmp(value, "y") == 0) {
209                         // Do nothing
210         } else if (strcasecmp(value, "no") == 0 ||
211                 strcasecmp(value, "n") == 0) {
212                         Settings.HacksEnabled = FALSE;
213         } else {
214                 // Hack: the user probably wants to enable hacks
215                 // and use this argument as the ROM file.
216                 // Wonder why popt does not support this or if there's a better way.
217                 S9xSetRomFile(value);
218         }
219 }
220
221 static void loadConfig(poptContext optCon, const char * file)
222 {
223         char * out;
224         int newargc, ret;
225         const char ** newargv;
226         FILE * fp;
227
228         fp = fopen (file, "r");
229         if (!fp) {
230                 fprintf(stderr, "Cannot open config file %s\n", file);
231                 return;
232         }
233
234         ret = poptConfigFileToString (fp, &out, 0);
235         if (ret)
236             DIE("Cannot parse config file %s. ret=%d\n", file, ret);
237
238         poptParseArgvString(out, &newargc, &newargv);
239
240         poptStuffArgs(optCon, newargv);
241
242         free(out);
243         fclose(fp);
244         /* XXX: currently leaking newargv */
245 }
246
247 static void parseArgs(poptContext optCon)
248 {
249         int rc;
250         unsigned char scancode = 0;
251         
252         while ((rc = poptGetNextOpt(optCon)) > 0) {
253                 const char * val;
254                 switch (rc) {
255                         case 1:
256                                 Config.enableAudio = false;
257                                 break;
258                         case 2:
259                                 Settings.DisplayFrameRate = TRUE;
260                                 break;
261                         case 3:
262                                 Settings.SkipFrames = atoi(poptGetOptArg(optCon));
263                                 break;
264                         case 4:
265                                 Config.fullscreen = true;
266                                 break;
267                         case 5:
268                                 Settings.SixteenBit = TRUE;
269                                 Settings.Transparency = TRUE;
270                                 break;
271                         case 6:
272                                 Settings.HacksEnabled = TRUE;
273                                 setHacks(poptGetOptArg(optCon));
274                                 break;
275                         case 7:
276                                 Settings.ForcePAL = TRUE;
277                                 break;
278                         case 8:
279                                 Settings.ForceNTSC = TRUE;
280                                 break;
281                         case 9:
282                                 Settings.TurboMode = TRUE;
283                                 break;
284                         case 10:
285                                 loadConfig(optCon, poptGetOptArg(optCon));
286                                 break;
287                         case 11:
288                                 val = poptGetOptArg(optCon);
289                                 Settings.Mouse = TRUE;
290                                 if (!val || atoi(val) <= 1) {
291                                         // Enable mouse on first controller
292                                         Settings.ControllerOption = SNES_MOUSE_SWAPPED;
293                                 } else {
294                                         // Enable mouse on second controller
295                                         Settings.ControllerOption = SNES_MOUSE;
296                                 }
297                                 break;
298                         case 12:
299                                 Settings.SuperScope = TRUE;
300                                 Settings.ControllerOption = SNES_SUPERSCOPE;
301                                 break;
302                         case 100:
303                                 scancode = atoi(poptGetOptArg(optCon));
304                                 break;
305                         case 101:
306                                 Config.joypad1Mapping[scancode] |= 
307                                         buttonNameToBit(poptGetOptArg(optCon));
308                                 break;
309                         case 102:
310                                 Config.action[scancode] |= 
311                                         actionNameToBit(poptGetOptArg(optCon));
312                                 break;
313                         case 200:
314                                 strcpy(Config.hacksFile, poptGetOptArg(optCon));
315                                 break;
316                         default:
317                                 DIE("Invalid popt argument (this is a bug): %d", rc);
318                                 break;
319                 }
320         }
321         
322         if (rc < -1) {
323                 /* an error occurred during option processing */
324                 fprintf(stderr, "%s: %s\n",
325                         poptBadOption(optCon, 0),
326                         poptStrerror(rc));
327                 exit(2);
328         }
329
330         /* if there's an extra unparsed arg it's our rom file */
331         const char * extra_arg = poptGetArg(optCon);
332         if (extra_arg) 
333                 S9xSetRomFile(extra_arg);
334 }
335
336 void S9xLoadConfig(int argc, const char ** argv)
337 {
338         poptContext optCon =
339                 poptGetContext("drnoksnes", argc, argv, optionsTable, 0);
340         poptSetOtherOptionHelp(optCon, "<rom>");
341
342         // Builtin defaults
343         loadDefaults();
344
345         // Read config file ~/apps/DrNokSnes.txt
346         char defConfFile[PATH_MAX];
347         sprintf(defConfFile, "%s/%s", getenv("HOME"), "apps/DrNokSnes.txt");
348         loadConfig(optCon, defConfFile);
349
350         // Command line parameters (including --conf args)
351         parseArgs(optCon);
352
353         if (!gotRomFile() && !hgwLaunched) {
354                 // User did not specify a ROM file, 
355                 // and we're not being launched from D-Bus.
356                 fprintf(stderr, "You need to specify a ROM, like this:\n");
357                 poptPrintUsage(optCon, stdout, 0);
358                 poptFreeContext(optCon); 
359                 exit(2);
360         }
361
362         poptFreeContext(optCon);
363 }
364