1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 
6 
7 #include "conf.h"
8 
9 #include "naev.h"
10 
11 #include <stdlib.h> /* atoi */
12 #include <unistd.h> /* getopt */
13 #include "nstring.h" /* strdup */
14 #include <getopt.h> /* getopt_long */
15 
16 #include "nlua.h"
17 
18 #include "log.h"
19 #include "player.h"
20 #include "input.h"
21 #include "opengl.h"
22 #include "music.h"
23 #include "nebula.h"
24 #include "ndata.h"
25 #include "nfile.h"
26 #include "nstring.h"
27 
28 
29 #define  conf_loadInt(n,i)    \
30 nlua_getenv(env,n); \
31 if (lua_isnumber(naevL, -1)) { \
32    i = (int)lua_tonumber(naevL, -1); \
33 } \
34 lua_pop(naevL,1);
35 
36 #define  conf_loadFloat(n,f)    \
37 nlua_getenv(env,n); \
38 if (lua_isnumber(naevL, -1)) { \
39    f = (double)lua_tonumber(naevL, -1); \
40 } \
41 lua_pop(naevL,1);
42 
43 #define  conf_loadBool(n,b)   \
44 nlua_getenv(env, n); \
45 if (lua_isnumber(naevL,-1)) \
46    b = (lua_tonumber(naevL,-1) != 0.); \
47 else if (!lua_isnil(naevL,-1)) \
48    b = lua_toboolean(naevL, -1); \
49 lua_pop(naevL,1);
50 
51 #define  conf_loadString(n,s) \
52 nlua_getenv(env, n); \
53 if (lua_isstring(naevL, -1)) { \
54    if (s != NULL) \
55       free(s); \
56    s = strdup(lua_tostring(naevL, -1));   \
57 } \
58 lua_pop(naevL,1);
59 
60 
61 /* Global configuration. */
62 PlayerConf_t conf = { .ndata = NULL, .sound_backend = NULL, .joystick_nam = NULL };
63 
64 /* from main.c */
65 extern int show_fps;
66 extern int max_fps;
67 extern int indjoystick;
68 extern char* namjoystick;
69 /* from player.c */
70 extern const char *keybind_info[][3]; /* keybindings */
71 /* from input.c */
72 extern unsigned int input_afterburnSensitivity;
73 
74 
75 /*
76  * prototypes
77  */
78 static void print_usage( char **argv );
79 
80 
81 /*
82  * prints usage
83  */
print_usage(char ** argv)84 static void print_usage( char **argv )
85 {
86    LOG("Usage: %s [OPTIONS] [DATA]", argv[0]);
87    LOG("Options are:");
88    LOG("   -f, --fullscreen      activate fullscreen");
89    LOG("   -F n, --fps n         limit frames per second to n");
90    LOG("   -V, --vsync           enable vsync");
91    LOG("   -W n                  set width to n");
92    LOG("   -H n                  set height to n");
93    LOG("   -j n, --joystick n    use joystick n");
94    LOG("   -J s, --Joystick s    use joystick whose name contains s");
95    LOG("   -M, --mute            disables sound");
96    LOG("   -S, --sound           forces sound");
97    LOG("   -m f, --mvol f        sets the music volume to f");
98    LOG("   -s f, --svol f        sets the sound volume to f");
99    LOG("   -G, --generate        regenerates the nebula (slow)");
100    LOG("   -N, --nondata         do not use ndata and try to use laid out files");
101    LOG("   -d, --datapath        specifies a custom path for all user data (saves, screenshots, etc.)");
102    LOG("   -X, --scale           defines the scale factor");
103 #ifdef DEBUGGING
104    LOG("   --devmode             enables dev mode perks like the editors");
105    LOG("   --devcsv              generates csv output from the ndata for development purposes");
106 #endif /* DEBUGGING */
107    LOG("   -h, --help            display this message and exit");
108    LOG("   -v, --version         print the version and exit");
109 }
110 
111 
112 /**
113  * @brief Sets the default configuration.
114  */
conf_setDefaults(void)115 void conf_setDefaults (void)
116 {
117    conf_cleanup();
118 
119    /* ndata. */
120    if (conf.ndata != NULL)
121       free(conf.ndata);
122    conf.ndata        = NULL;
123 
124    /* Joystick. */
125    conf.joystick_ind = -1;
126    if (conf.joystick_nam != NULL)
127       free(conf.joystick_nam);
128    conf.joystick_nam = NULL;
129 
130    /* GUI. */
131    conf.mesg_visible = 5;
132 
133    /* Repeat. */
134    conf.repeat_delay = 500;
135    conf.repeat_freq  = 30;
136 
137    /* Dynamic zoom. */
138    conf.zoom_manual  = 0;
139    conf.zoom_far     = 0.5;
140    conf.zoom_near    = 1.;
141    conf.zoom_speed   = 0.25;
142    conf.zoom_stars   = 1.;
143 
144    /* Font sizes. */
145    conf.font_size_console = 10;
146    conf.font_size_intro   = 18;
147    conf.font_size_def     = 12;
148    conf.font_size_small   = 10;
149 
150    /* Misc. */
151    conf.redirect_file = 1;
152    conf.nosave       = 0;
153    conf.devmode      = 0;
154    conf.devautosave  = 0;
155    conf.devcsv       = 0;
156 
157    /* Gameplay. */
158    conf_setGameplayDefaults();
159 
160    /* Audio. */
161    conf_setAudioDefaults();
162 
163    /* Video. */
164    conf_setVideoDefaults();
165 
166    /* Input */
167    input_setDefault(1);
168 
169    /* Debugging. */
170    conf.fpu_except   = 0; /* Causes many issues. */
171 
172    /* Editor. */
173    if (conf.dev_save_sys != NULL)
174       free( conf.dev_save_sys );
175    conf.dev_save_sys = strdup( DEV_SAVE_SYSTEM_DEFAULT );
176    if (conf.dev_save_map != NULL)
177       free( conf.dev_save_map );
178    conf.dev_save_map = strdup( DEV_SAVE_MAP_DEFAULT );
179    if (conf.dev_save_asset != NULL)
180       free( conf.dev_save_asset );
181    conf.dev_save_asset = strdup( DEV_SAVE_ASSET_DEFAULT );
182 }
183 
184 
185 /**
186  * @brief Sets the gameplay defaults.
187  */
conf_setGameplayDefaults(void)188 void conf_setGameplayDefaults (void)
189 {
190    conf.afterburn_sens        = AFTERBURNER_SENSITIVITY_DEFAULT;
191    conf.compression_velocity  = TIME_COMPRESSION_DEFAULT_MAX;
192    conf.compression_mult      = TIME_COMPRESSION_DEFAULT_MULT;
193    conf.save_compress         = SAVE_COMPRESSION_DEFAULT;
194    conf.mouse_thrust          = MOUSE_THRUST_DEFAULT;
195    conf.mouse_doubleclick     = MOUSE_DOUBLECLICK_TIME;
196    conf.autonav_reset_speed   = AUTONAV_RESET_SPEED_DEFAULT;
197    conf.zoom_manual           = MANUAL_ZOOM_DEFAULT;
198 }
199 
200 
201 /**
202  * @brief Sets the audio defaults.
203  */
conf_setAudioDefaults(void)204 void conf_setAudioDefaults (void)
205 {
206    if (conf.sound_backend != NULL) {
207       free(conf.sound_backend);
208       conf.sound_backend = NULL;
209    }
210 
211    /* Sound. */
212    conf.sound_backend = strdup(BACKEND_DEFAULT);
213    conf.snd_voices   = VOICES_DEFAULT;
214    conf.snd_pilotrel = PILOT_RELATIVE_DEFAULT;
215    conf.al_efx       = USE_EFX_DEFAULT;
216    conf.al_bufsize   = BUFFER_SIZE_DEFAULT;
217    conf.nosound      = MUTE_SOUND_DEFAULT;
218    conf.sound        = SOUND_VOLUME_DEFAULT;
219    conf.music        = MUSIC_VOLUME_DEFAULT;
220 }
221 
222 
223 /**
224  * @brief Sets the video defaults.
225  */
conf_setVideoDefaults(void)226 void conf_setVideoDefaults (void)
227 {
228    int w, h, f;
229 
230    /* More complex resolution handling. */
231    f = 0;
232    if ((gl_screen.desktop_w > 0) && (gl_screen.desktop_h > 0)) {
233       /* Try higher resolution. */
234       w = RESOLUTION_W_DEFAULT;
235       h = RESOLUTION_H_DEFAULT;
236 
237       /* Fullscreen and fit everything onscreen. */
238       if ((gl_screen.desktop_w <= w) || (gl_screen.desktop_h <= h)) {
239          w = gl_screen.desktop_w;
240          h = gl_screen.desktop_h;
241          f = FULLSCREEN_DEFAULT;
242       }
243    }
244    else {
245       w = 800;
246       h = 600;
247    }
248 
249    /* OpenGL. */
250    conf.fsaa         = FSAA_DEFAULT;
251    conf.vsync        = VSYNC_DEFAULT;
252    conf.vbo          = VBO_DEFAULT; /* Seems to cause a lot of issues. */
253    conf.mipmaps      = MIPMAP_DEFAULT; /* Also cause for issues. */
254    conf.compress     = TEXTURE_COMPRESSION_DEFAULT;
255    conf.interpolate  = INTERPOLATION_DEFAULT;
256    conf.npot         = NPOT_TEXTURES_DEFAULT;
257 
258    /* Window. */
259    conf.fullscreen   = f;
260    conf.width        = w;
261    conf.height       = h;
262    conf.explicit_dim = 0; /* No need for a define, this is only for first-run. */
263    conf.scalefactor  = SCALE_FACTOR_DEFAULT;
264    conf.minimize     = MINIMIZE_DEFAULT;
265 
266    /* FPS. */
267    conf.fps_show     = SHOW_FPS_DEFAULT;
268    conf.fps_max      = FPS_MAX_DEFAULT;
269 
270    /* Pause. */
271    conf.pause_show   = SHOW_PAUSE_DEFAULT;
272 
273    /* Memory. */
274    conf.engineglow   = ENGINE_GLOWS_DEFAULT;
275 }
276 
277 
278 /*
279  * Frees some memory the conf allocated.
280  */
conf_cleanup(void)281 void conf_cleanup (void)
282 {
283    if (conf.ndata != NULL)
284       free(conf.ndata);
285    if (conf.sound_backend != NULL)
286       free(conf.sound_backend);
287    if (conf.joystick_nam != NULL)
288       free(conf.joystick_nam);
289 
290    if (conf.dev_save_sys != NULL)
291       free(conf.dev_save_sys);
292    if (conf.dev_save_map != NULL)
293       free(conf.dev_save_map);
294    if (conf.dev_save_asset != NULL)
295       free(conf.dev_save_asset);
296 
297    /* Clear memory. */
298    memset( &conf, 0, sizeof(conf) );
299 }
300 
301 
302 /*
303  * @brief Parses the local conf that dictates where user data goes.
304  */
conf_loadConfigPath(void)305 void conf_loadConfigPath( void )
306 {
307    const char *file = "datapath.lua";
308 
309    if (!nfile_fileExists(file))
310       return;
311 
312    nlua_env env = nlua_newEnv(0);
313    if (nlua_dofileenv(env, file) == 0)
314       conf_loadString("datapath",conf.datapath);
315 
316    nlua_freeEnv(env);
317 }
318 
319 
320 /*
321  * parses the config file
322  */
conf_loadConfig(const char * file)323 int conf_loadConfig ( const char* file )
324 {
325    int i, t;
326    const char *str, *mod;
327    SDLKey key;
328    int type;
329    int w,h;
330    SDLMod m;
331 
332    /* Check to see if file exists. */
333    if (!nfile_fileExists(file))
334       return nfile_touch(file);
335 
336    /* Load the configuration. */
337    nlua_env env = nlua_newEnv(0);
338    if (nlua_dofileenv(env, file) == 0) {
339 
340       /* ndata. */
341       conf_loadString("data",conf.ndata);
342 
343       /* OpenGL. */
344       conf_loadInt("fsaa",conf.fsaa);
345       conf_loadBool("vsync",conf.vsync);
346       conf_loadBool("vbo",conf.vbo);
347       conf_loadBool("mipmaps",conf.mipmaps);
348       conf_loadBool("compress",conf.compress);
349       conf_loadBool("interpolate",conf.interpolate);
350       conf_loadBool("npot",conf.npot);
351 
352       /* Memory. */
353       conf_loadBool("engineglow",conf.engineglow);
354 
355       /* Window. */
356       w = h = 0;
357       conf_loadInt("width",w);
358       conf_loadInt("height",h);
359       if (w != 0) {
360          conf.explicit_dim = 1;
361          conf.width = w;
362       }
363       if (h != 0) {
364          conf.explicit_dim = 1;
365          conf.height = h;
366       }
367       conf_loadFloat("scalefactor",conf.scalefactor);
368       conf_loadBool("fullscreen",conf.fullscreen);
369       conf_loadBool("modesetting",conf.modesetting);
370       conf_loadBool("minimize",conf.minimize);
371 
372       /* FPS */
373       conf_loadBool("showfps",conf.fps_show);
374       conf_loadInt("maxfps",conf.fps_max);
375 
376       /*  Pause */
377       conf_loadBool("showpause",conf.pause_show);
378 
379       /* Sound. */
380       conf_loadString("sound_backend",conf.sound_backend);
381       conf_loadInt("snd_voices",conf.snd_voices);
382       conf.snd_voices = MAX( 16, conf.snd_voices ); /* Must be at least 16. */
383       conf_loadBool("snd_pilotrel",conf.snd_pilotrel);
384       conf_loadBool("al_efx",conf.al_efx);
385       conf_loadInt("al_bufsize", conf.al_bufsize);
386       conf_loadBool("nosound",conf.nosound);
387       conf_loadFloat("sound",conf.sound);
388       conf_loadFloat("music",conf.music);
389 
390       /* Joystick. */
391       nlua_getenv(env, "joystick");
392       if (lua_isnumber(naevL, -1))
393          conf.joystick_ind = (int)lua_tonumber(naevL, -1);
394       else if (lua_isstring(naevL, -1))
395          conf.joystick_nam = strdup(lua_tostring(naevL, -1));
396       lua_pop(naevL,1);
397 
398       /* GUI. */
399       conf_loadInt("mesg_visible",conf.mesg_visible);
400       if (conf.mesg_visible <= 0)
401          conf.mesg_visible = 5;
402 
403       /* Key repeat. */
404       conf_loadInt("repeat_delay",conf.repeat_delay);
405       conf_loadInt("repeat_freq",conf.repeat_freq);
406 
407       /* Zoom. */
408       conf_loadBool("zoom_manual",conf.zoom_manual);
409       conf_loadFloat("zoom_far",conf.zoom_far);
410       conf_loadFloat("zoom_near",conf.zoom_near);
411       conf_loadFloat("zoom_speed",conf.zoom_speed);
412       conf_loadFloat("zoom_stars",conf.zoom_stars);
413 
414       /* Font size. */
415       conf_loadInt("font_size_console",conf.font_size_console);
416       conf_loadInt("font_size_intro",conf.font_size_intro);
417       conf_loadInt("font_size_def",conf.font_size_def);
418       conf_loadInt("font_size_small",conf.font_size_small);
419 
420       /* Misc. */
421       conf_loadFloat("compression_velocity",conf.compression_velocity);
422       conf_loadFloat("compression_mult",conf.compression_mult);
423       conf_loadBool("redirect_file",conf.redirect_file);
424       conf_loadBool("save_compress",conf.save_compress);
425       conf_loadInt("afterburn_sensitivity",conf.afterburn_sens);
426       conf_loadInt("mouse_thrust",conf.mouse_thrust);
427       conf_loadFloat("mouse_doubleclick",conf.mouse_doubleclick);
428       conf_loadFloat("autonav_abort",conf.autonav_reset_speed);
429       conf_loadBool("devmode",conf.devmode);
430       conf_loadBool("devautosave",conf.devautosave);
431       conf_loadBool("conf_nosave",conf.nosave);
432 
433       /* Debugging. */
434       conf_loadBool("fpu_except",conf.fpu_except);
435 
436       /* Editor. */
437       conf_loadString("dev_save_sys",conf.dev_save_sys);
438       conf_loadString("dev_save_map",conf.dev_save_map);
439       conf_loadString("dev_save_asset",conf.dev_save_asset);
440 
441       /*
442        * Keybindings.
443        */
444       for (i=0; strcmp(keybind_info[i][0],"end"); i++) {
445          nlua_getenv(env, keybind_info[i][0]);
446          /* Handle "none". */
447          if (lua_isstring(naevL,-1)) {
448             str = lua_tostring(naevL,-1);
449             if (strcmp(str,"none")==0) {
450                input_setKeybind( keybind_info[i][0],
451                      KEYBIND_NULL, SDLK_UNKNOWN, NMOD_NONE );
452             }
453          }
454          else if (lua_istable(naevL, -1)) { /* it's a table */
455             /* gets the event type */
456             lua_pushstring(naevL, "type");
457             lua_gettable(naevL, -2);
458             if (lua_isstring(naevL, -1))
459                str = lua_tostring(naevL, -1);
460             else if (lua_isnil(naevL, -1)) {
461                WARN("Found keybind with no type field!");
462                str = "null";
463             }
464             else {
465                WARN("Found keybind with invalid type field!");
466                str = "null";
467             }
468             lua_pop(naevL,1);
469 
470             /* gets the key */
471             lua_pushstring(naevL, "key");
472             lua_gettable(naevL, -2);
473             t = lua_type(naevL, -1);
474             if (t == LUA_TNUMBER)
475                key = (int)lua_tonumber(naevL, -1);
476             else if (t == LUA_TSTRING)
477                key = input_keyConv( lua_tostring(naevL, -1));
478             else if (t == LUA_TNIL) {
479                WARN("Found keybind with no key field!");
480                key = SDLK_UNKNOWN;
481             }
482             else {
483                WARN("Found keybind with invalid key field!");
484                key = SDLK_UNKNOWN;
485             }
486             lua_pop(naevL,1);
487 
488             /* Get the modifier. */
489             lua_pushstring(naevL, "mod");
490             lua_gettable(naevL, -2);
491             if (lua_isstring(naevL, -1))
492                mod = lua_tostring(naevL, -1);
493             else
494                mod = NULL;
495             lua_pop(naevL,1);
496 
497             if (str != NULL) { /* keybind is valid */
498                if (key == SDLK_UNKNOWN) {
499                   WARN("Keybind for '%s' is invalid", keybind_info[i][0]);
500                   continue;
501                }
502 
503                /* get type */
504                if (strcmp(str,"null")==0)          type = KEYBIND_NULL;
505                else if (strcmp(str,"keyboard")==0) type = KEYBIND_KEYBOARD;
506                else if (strcmp(str,"jaxispos")==0) type = KEYBIND_JAXISPOS;
507                else if (strcmp(str,"jaxisneg")==0) type = KEYBIND_JAXISNEG;
508                else if (strcmp(str,"jbutton")==0)  type = KEYBIND_JBUTTON;
509                else {
510                   WARN("Unknown keybinding of type %s", str);
511                   continue;
512                }
513 
514                /* Set modifier, probably should be able to handle two at a time. */
515                if (mod != NULL) {
516                   /* The "rctrl/lctrl" friends are for compat with 0.4.0 and older, remove around 0.5.0 or so. */
517                   if      (strcmp(mod,"ctrl")==0)    m = NMOD_CTRL;
518                   else if (strcmp(mod,"lctrl")==0)   m = NMOD_CTRL; /* compat. */
519                   else if (strcmp(mod,"rctrl")==0)   m = NMOD_CTRL; /* compat. */
520                   else if (strcmp(mod,"shift")==0)   m = NMOD_SHIFT;
521                   else if (strcmp(mod,"lshift")==0)  m = NMOD_SHIFT; /* compat. */
522                   else if (strcmp(mod,"rshift")==0)  m = NMOD_SHIFT; /* compat. */
523                   else if (strcmp(mod,"alt")==0)     m = NMOD_ALT;
524                   else if (strcmp(mod,"lalt")==0)    m = NMOD_ALT; /* compat. */
525                   else if (strcmp(mod,"ralt")==0)    m = NMOD_ALT; /* compat. */
526                   else if (strcmp(mod,"meta")==0)    m = NMOD_META;
527                   else if (strcmp(mod,"lmeta")==0)   m = NMOD_META; /* compat. */
528                   else if (strcmp(mod,"rmeta")==0)   m = NMOD_META; /* compat. */
529                   else if (strcmp(mod,"any")==0)     m = NMOD_ALL;
530                   else if (strcmp(mod,"none")==0)    m = NMOD_NONE;
531                   else {
532                      WARN("Unknown keybinding mod of type %s", mod);
533                      m = NMOD_NONE;
534                   }
535                }
536                else
537                   m = NMOD_NONE;
538 
539                /* set the keybind */
540                input_setKeybind( keybind_info[i][0], type, key, m );
541             }
542             else
543                WARN("Malformed keybind for '%s' in '%s'.", keybind_info[i][0], file);
544          }
545          /* clean up after table stuff */
546          lua_pop(naevL,1);
547       }
548    }
549    else { /* failed to load the config file */
550       WARN("Config file '%s' has invalid syntax:", file );
551       WARN("   %s", lua_tostring(naevL,-1));
552       nlua_freeEnv(env);
553       return 1;
554    }
555 
556    nlua_freeEnv(env);
557    return 0;
558 }
559 
560 
conf_parseCLIPath(int argc,char ** argv)561 void conf_parseCLIPath( int argc, char** argv )
562 {
563    static struct option long_options[] = {
564       { "datapath", required_argument, 0, 'd' },
565       { NULL, 0, 0, 0 }
566    };
567 
568    int option_index = 1;
569    int c = 0;
570 
571    /* GNU giveth, and GNU taketh away.
572     * If we don't specify "-" as the first char, getopt will happily
573     * mangle the initial argument order, probably causing crashes when
574     * passing arguments that take values, such as -H and -W.
575     */
576    while ((c = getopt_long(argc, argv, "-:d:",
577          long_options, &option_index)) != -1) {
578       switch(c) {
579          case 'd':
580             conf.datapath = strdup(optarg);
581             break;
582       }
583    }
584 }
585 
586 
587 /*
588  * parses the CLI options
589  */
conf_parseCLI(int argc,char ** argv)590 void conf_parseCLI( int argc, char** argv )
591 {
592    static struct option long_options[] = {
593       { "datapath", required_argument, 0, 'd' },
594       { "fullscreen", no_argument, 0, 'f' },
595       { "fps", required_argument, 0, 'F' },
596       { "vsync", no_argument, 0, 'V' },
597       { "joystick", required_argument, 0, 'j' },
598       { "Joystick", required_argument, 0, 'J' },
599       { "width", required_argument, 0, 'W' },
600       { "height", required_argument, 0, 'H' },
601       { "mute", no_argument, 0, 'M' },
602       { "sound", no_argument, 0, 'S' },
603       { "mvol", required_argument, 0, 'm' },
604       { "svol", required_argument, 0, 's' },
605       { "generate", no_argument, 0, 'G' },
606       { "nondata", no_argument, 0, 'N' },
607       { "scale", required_argument, 0, 'X' },
608 #ifdef DEBUGGING
609       { "devmode", no_argument, 0, 'D' },
610       { "devcsv", no_argument, 0, 'C' },
611 #endif /* DEBUGGING */
612       { "help", no_argument, 0, 'h' },
613       { "version", no_argument, 0, 'v' },
614       { NULL, 0, 0, 0 } };
615    int option_index = 1;
616    int c = 0;
617 
618    /* man 3 getopt says optind should be initialized to 1, but that seems to
619     * cause all options to get parsed, i.e. we cannot detect a trailing ndata
620     * option.
621     */
622    optind = 0;
623    while ((c = getopt_long(argc, argv,
624          "fF:Vd:j:J:W:H:MSm:s:X:GNhv",
625          long_options, &option_index)) != -1) {
626       switch (c) {
627          case 'd':
628             /* Does nothing, datapath is parsed earlier. */
629             break;
630          case 'f':
631             conf.fullscreen = 1;
632             break;
633          case 'F':
634             conf.fps_max = atoi(optarg);
635             break;
636          case 'V':
637             conf.vsync = 1;
638             break;
639          case 'j':
640             conf.joystick_ind = atoi(optarg);
641             break;
642          case 'J':
643             conf.joystick_nam = strdup(optarg);
644             break;
645          case 'W':
646             conf.width = atoi(optarg);
647             conf.explicit_dim = 1;
648             break;
649          case 'H':
650             conf.height = atoi(optarg);
651             conf.explicit_dim = 1;
652             break;
653          case 'M':
654             conf.nosound = 1;
655             break;
656          case 'S':
657             conf.nosound = 0;
658             break;
659          case 'm':
660             conf.music = atof(optarg);
661             break;
662          case 's':
663             conf.sound = atof(optarg);
664             break;
665          case 'G':
666             nebu_forceGenerate();
667             break;
668          case 'N':
669             if (conf.ndata != NULL)
670                free(conf.ndata);
671             conf.ndata = NULL;
672             break;
673          case 'X':
674             conf.scalefactor = atof(optarg);
675             break;
676 #ifdef DEBUGGING
677          case 'D':
678             conf.devmode = 1;
679             LOG("Enabling developer mode.");
680             break;
681 
682          case 'C':
683             conf.devcsv = 1;
684             LOG("Will generate CSV output.");
685             break;
686 #endif /* DEBUGGING */
687 
688          case 'v':
689             /* by now it has already displayed the version */
690             exit(EXIT_SUCCESS);
691          case 'h':
692             print_usage(argv);
693             exit(EXIT_SUCCESS);
694       }
695    }
696 
697    /** @todo handle multiple ndata. */
698    if (optind < argc)
699       conf.ndata = strdup( argv[ optind ] );
700 }
701 
702 
703 /**
704  * @brief nsnprintf-like function to quote and escape a string for use in Lua source code
705  *
706  *    @param str The destination buffer
707  *    @param size The maximum amount of space in str to use
708  *    @param text The string to quote and escape
709  *    @return The number of characters actually written to str
710  */
quoteLuaString(char * str,size_t size,const char * text)711 static size_t quoteLuaString(char *str, size_t size, const char *text)
712 {
713    const unsigned char *in;
714    char slashescape;
715    size_t count;
716 
717    if (size == 0)
718       return 0;
719 
720    /* Write a Lua nil if we are given a NULL pointer */
721    if (text == NULL)
722       return nsnprintf(str, size, "nil");
723 
724    count = 0;
725 
726    /* Quote start */
727    str[count++] = '\"';
728    if (count == size)
729       return count;
730 
731    /* Iterate over the characters in text */
732    for (in = (const unsigned char *)text; *in != '\0'; in++) {
733       /* Check if we can print this as a friendly backslash-escape */
734       switch (*in) {
735          case '\a':  slashescape = 'a';   break;
736          case '\b':  slashescape = 'b';   break;
737          case '\f':  slashescape = 'f';   break;
738          case '\n':  slashescape = 'n';   break;
739          case '\r':  slashescape = 'r';   break;
740          case '\t':  slashescape = 't';   break;
741          case '\v':  slashescape = 'v';   break;
742          case '\\':  slashescape = '\\';  break;
743          case '\"':  slashescape = '\"';  break;
744          case '\'':  slashescape = '\'';  break;
745          /* Technically, Lua can also represent \0, but we can't in our input */
746          default:    slashescape = 0;     break;
747       }
748       if (slashescape != 0)
749       {
750          /* Yes, we can use a backslash-escape! */
751          str[count++] = '\\';
752          if (count == size)
753             return count;
754 
755          str[count++] = slashescape;
756          if (count == size)
757             return count;
758 
759          continue;
760       }
761 
762       /* Check if this is an otherwise printable ASCII character */
763       if (*in >= 0x20 && *in <= 0x7E)
764       {
765          /* Write it straight to the output if so */
766          str[count++] = *in;
767          if (count == size)
768             return count;
769 
770          continue;
771       }
772 
773       /* Otherwise, escape the character using a \ddd sequence */
774       str[count++] = '\\';
775       if (count == size)
776          return count;
777 
778       count += nsnprintf(&str[count], size-count, "%03u", *in);
779       if (count == size)
780          return count;
781    }
782 
783    /* Quote end */
784    str[count++] = '\"';
785    if (count == size)
786       return count;
787 
788    /* zero-terminate, if possible */
789    if (count != size)
790       str[count] = '\0';   /* don't increase count, like nsnprintf */
791 
792    /* return the amount of characters written */
793    return count;
794 }
795 
796 
797 #define  conf_saveComment(t)     \
798 pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "-- %s\n", t);
799 
800 #define  conf_saveEmptyLine()     \
801 if (sizeof(buf) != pos) \
802    buf[pos++] = '\n';
803 
804 #define  conf_saveInt(n,i)    \
805 pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = %d\n", n, i);
806 
807 #define  conf_saveFloat(n,f)    \
808 pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = %f\n", n, f);
809 
810 #define  conf_saveBool(n,b)    \
811 if (b) \
812    pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = true\n", n); \
813 else \
814    pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = false\n", n);
815 
816 #define  conf_saveString(n,s) \
817 pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = ", n); \
818 pos += quoteLuaString(&buf[pos], sizeof(buf)-pos, s); \
819 if (sizeof(buf) != pos) \
820    buf[pos++] = '\n';
821 
822 #define GENERATED_START_COMMENT  "START GENERATED SECTION"
823 #define GENERATED_END_COMMENT    "END GENERATED SECTION"
824 
825 
826 /*
827  * saves the current configuration
828  */
conf_saveConfig(const char * file)829 int conf_saveConfig ( const char* file )
830 {
831    int i;
832    char *old;
833    const char *oldfooter;
834    int oldsize;
835    char buf[32*1024];
836    size_t pos;
837    SDLKey key;
838    char keyname[17];
839    KeybindType type;
840    const char *typename;
841    SDLMod mod;
842    const char *modname;
843 
844    pos         = 0;
845    oldfooter   = NULL;
846 
847    /* User doesn't want to save the config. */
848    if (conf.nosave)
849       return 0;
850 
851    /* Read the old configuration, if possible */
852    if (nfile_fileExists(file) && (old = nfile_readFile(&oldsize, file)) != NULL) {
853       /* See if we can find the generated section and preserve
854        * whatever the user wrote before it */
855       const char *tmp = nstrnstr(old, "-- "GENERATED_START_COMMENT"\n", oldsize);
856       if (tmp != NULL) {
857          /* Copy over the user content */
858          pos = MIN(sizeof(buf), (size_t)(tmp - old));
859          memcpy(buf, old, pos);
860 
861          /* See if we can find the end of the section */
862          tmp = nstrnstr(tmp, "-- "GENERATED_END_COMMENT"\n", oldsize-pos);
863          if (tmp != NULL) {
864             /* Everything after this should also be preserved */
865             oldfooter = tmp + strlen("-- "GENERATED_END_COMMENT"\n");
866             oldsize -= (oldfooter - old);
867          }
868       }
869       else {
870          /* Treat the contents of the old file as a footer. */
871          oldfooter = old;
872       }
873    }
874    else {
875       old = NULL;
876 
877       /* Write a nice header for new configuration files */
878       conf_saveComment(APPNAME " configuration file");
879       conf_saveEmptyLine();
880    }
881 
882    /* Back up old configuration. */
883    if (nfile_backupIfExists(file) < 0) {
884       WARN("Not saving configuration.");
885       return -1;
886    }
887 
888    /* Header. */
889    conf_saveComment(GENERATED_START_COMMENT);
890    conf_saveComment("The contents of this section will be rewritten by "APPNAME"!");
891    conf_saveEmptyLine();
892 
893    /* ndata. */
894    conf_saveComment("The location of "APPNAME"'s data pack, usually called 'ndata'");
895    conf_saveString("data",conf.ndata);
896    conf_saveEmptyLine();
897 
898    /* OpenGL. */
899    conf_saveComment("The factor to use in Full-Scene Anti-Aliasing");
900    conf_saveComment("Anything lower than 2 will simply disable FSAA");
901    conf_saveInt("fsaa",conf.fsaa);
902    conf_saveEmptyLine();
903 
904    conf_saveComment("Synchronize framebuffer updates with the vertical blanking interval");
905    conf_saveBool("vsync",conf.vsync);
906    conf_saveEmptyLine();
907 
908    conf_saveComment("Use OpenGL Vertex Buffer Objects extensions");
909    conf_saveBool("vbo",conf.vbo);
910    conf_saveEmptyLine();
911 
912    conf_saveComment("Use OpenGL MipMaps");
913    conf_saveBool("mipmaps",conf.mipmaps);
914    conf_saveEmptyLine();
915 
916    conf_saveComment("Use OpenGL Texture Compression");
917    conf_saveBool("compress",conf.compress);
918    conf_saveEmptyLine();
919 
920    conf_saveComment("Use OpenGL Texture Interpolation");
921    conf_saveBool("interpolate",conf.interpolate);
922    conf_saveEmptyLine();
923 
924    conf_saveComment("Use OpenGL Non-\"Power of Two\" textures if available");
925    conf_saveComment("Lowers memory usage by a lot, but may cause slow downs on some systems");
926    conf_saveBool("npot",conf.npot);
927    conf_saveEmptyLine();
928 
929    /* Memory. */
930    conf_saveComment("If true enables engine glow");
931    conf_saveBool("engineglow",conf.engineglow);
932    conf_saveEmptyLine();
933 
934    /* Window. */
935    conf_saveComment("The window size or screen resolution");
936    conf_saveComment("Set both of these to 0 to make "APPNAME" try the desktop resolution");
937    if (conf.explicit_dim) {
938       conf_saveInt("width",conf.width);
939       conf_saveInt("height",conf.height);
940    } else {
941       conf_saveInt("width",0);
942       conf_saveInt("height",0);
943    }
944    conf_saveEmptyLine();
945 
946    conf_saveComment("Factor used to divide the above resolution with");
947    conf_saveComment("This is used to lower the rendering resolution, and scale to the above");
948    conf_saveFloat("scalefactor",conf.scalefactor);
949    conf_saveEmptyLine();
950 
951    conf_saveComment("Run "APPNAME" in full-screen mode");
952    conf_saveBool("fullscreen",conf.fullscreen);
953    conf_saveEmptyLine();
954 
955    conf_saveComment("Use video modesetting when fullscreen is enabled (SDL2-only)");
956    conf_saveBool("modesetting",conf.modesetting);
957    conf_saveEmptyLine();
958 
959    conf_saveComment("Minimize on focus loss (SDL2-only)");
960    conf_saveBool("minimize",conf.minimize);
961    conf_saveEmptyLine();
962 
963    /* FPS */
964    conf_saveComment("Display a framerate counter");
965    conf_saveBool("showfps",conf.fps_show);
966    conf_saveEmptyLine();
967 
968    conf_saveComment("Limit the rendering framerate");
969    conf_saveInt("maxfps",conf.fps_max);
970    conf_saveEmptyLine();
971 
972    /* Pause */
973    conf_saveComment("Show 'PAUSED' on screen while paused");
974    conf_saveBool("showpause",conf.pause_show);
975    conf_saveEmptyLine();
976 
977    /* Sound. */
978    conf_saveComment("Sound backend (can be \"openal\" or \"sdlmix\")");
979    conf_saveString("sound_backend",conf.sound_backend);
980    conf_saveEmptyLine();
981 
982    conf_saveComment("Maxmimum number of simultaneous sounds to play, must be at least 16.");
983    conf_saveInt("snd_voices",conf.snd_voices);
984    conf_saveEmptyLine();
985 
986    conf_saveComment("Sets sound to be relative to pilot when camera is following a pilot instead of referenced to camera.");
987    conf_saveBool("snd_pilotrel",conf.snd_pilotrel);
988    conf_saveEmptyLine();
989 
990    conf_saveComment("Enables EFX extension for OpenAL backend.");
991    conf_saveBool("al_efx",conf.al_efx);
992    conf_saveEmptyLine();
993 
994    conf_saveComment("Size of the OpenAL music buffer (in kilobytes).");
995    conf_saveInt("al_bufsize",conf.al_bufsize);
996    conf_saveEmptyLine();
997 
998    conf_saveComment("Disable all sound");
999    conf_saveBool("nosound",conf.nosound);
1000    conf_saveEmptyLine();
1001 
1002    conf_saveComment("Volume of sound effects and music, between 0.0 and 1.0");
1003    conf_saveFloat("sound",(sound_disabled) ? conf.sound : sound_getVolume());
1004    conf_saveFloat("music",(music_disabled) ? conf.music : music_getVolume());
1005    conf_saveEmptyLine();
1006 
1007    /* Joystick. */
1008    conf_saveComment("The name or numeric index of the joystick to use");
1009    conf_saveComment("Setting this to nil disables the joystick support");
1010    if (conf.joystick_nam != NULL) {
1011       conf_saveString("joystick",conf.joystick_nam);
1012    }
1013    else if (conf.joystick_ind >= 0) {
1014       conf_saveInt("joystick",conf.joystick_ind);
1015    }
1016    else {
1017       conf_saveString("joystick",NULL);
1018    }
1019    conf_saveEmptyLine();
1020 
1021    /* GUI. */
1022    conf_saveComment("Number of lines visible in the comm window.");
1023    conf_saveInt("mesg_visible",conf.mesg_visible);
1024    conf_saveEmptyLine();
1025 
1026    /* Key repeat. */
1027    conf_saveComment("Delay in ms before starting to repeat (0 disables)");
1028    conf_saveInt("repeat_delay",conf.repeat_delay);
1029    conf_saveComment("Delay in ms between repeats once it starts to repeat");
1030    conf_saveInt("repeat_freq",conf.repeat_freq);
1031    conf_saveEmptyLine();
1032 
1033    /* Zoom. */
1034    conf_saveComment("Minimum and maximum zoom factor to use in-game");
1035    conf_saveComment("At 1.0, no sprites are scaled");
1036    conf_saveComment("zoom_far should be less then zoom_near");
1037    conf_saveBool("zoom_manual",conf.zoom_manual);
1038    conf_saveFloat("zoom_far",conf.zoom_far);
1039    conf_saveFloat("zoom_near",conf.zoom_near);
1040    conf_saveEmptyLine();
1041 
1042    conf_saveComment("Zooming speed in factor increments per second");
1043    conf_saveFloat("zoom_speed",conf.zoom_speed);
1044    conf_saveEmptyLine();
1045 
1046    conf_saveComment("Zooming modulation factor for the starry background");
1047    conf_saveFloat("zoom_stars",conf.zoom_stars);
1048    conf_saveEmptyLine();
1049 
1050    /* Fonts. */
1051    conf_saveComment("Font sizes (in pixels) for NAEV");
1052    conf_saveComment("Warning, setting to other than the default can cause visual glitches!");
1053    conf_saveComment("Console default: 10");
1054    conf_saveInt("font_size_console",conf.font_size_console);
1055    conf_saveComment("Intro default: 18");
1056    conf_saveInt("font_size_intro",conf.font_size_intro);
1057    conf_saveComment("Default size: 12");
1058    conf_saveInt("font_size_def",conf.font_size_def);
1059    conf_saveComment("Small size: 10");
1060    conf_saveInt("font_size_small",conf.font_size_small);
1061    conf_saveEmptyLine();
1062 
1063    /* Misc. */
1064    conf_saveComment("Sets the velocity (px/s) to compress up to when time compression is enabled.");
1065    conf_saveFloat("compression_velocity",conf.compression_velocity);
1066    conf_saveEmptyLine();
1067 
1068    conf_saveComment("Sets the multiplier to compress up to when time compression is enabled.");
1069    conf_saveFloat("compression_mult",conf.compression_mult);
1070    conf_saveEmptyLine();
1071 
1072    conf_saveComment("Redirects log and error output to files");
1073    conf_saveBool("redirect_file",conf.redirect_file);
1074    conf_saveEmptyLine();
1075 
1076    conf_saveComment("Enables compression on savegames");
1077    conf_saveBool("save_compress",conf.save_compress);
1078    conf_saveEmptyLine();
1079 
1080    conf_saveComment("Afterburner sensitivity");
1081    conf_saveInt("afterburn_sensitivity",conf.afterburn_sens);
1082    conf_saveEmptyLine();
1083 
1084    conf_saveComment("Mouse-flying thrust control");
1085    conf_saveInt("mouse_thrust",conf.mouse_thrust);
1086    conf_saveEmptyLine();
1087 
1088    conf_saveComment("Maximum interval to count as a double-click (0 disables).");
1089    conf_saveFloat("mouse_doubleclick",conf.mouse_doubleclick);
1090    conf_saveEmptyLine();
1091 
1092    conf_saveComment("Condition under which the autonav aborts.");
1093    conf_saveFloat("autonav_abort",conf.autonav_reset_speed);
1094    conf_saveEmptyLine();
1095 
1096    conf_saveComment("Enables developer mode (universe editor and the likes)");
1097    conf_saveBool("devmode",conf.devmode);
1098    conf_saveEmptyLine();
1099 
1100    conf_saveComment("Automatic saving for developer mode");
1101    conf_saveBool("devautosave",conf.devautosave);
1102    conf_saveEmptyLine();
1103 
1104    conf_saveComment("Save the config everytime game exits (rewriting this bit)");
1105    conf_saveInt("conf_nosave",conf.nosave);
1106    conf_saveEmptyLine();
1107 
1108    /* Debugging. */
1109    conf_saveComment("Enables FPU exceptions - only works on DEBUG builds");
1110    conf_saveBool("fpu_except",conf.fpu_except);
1111    conf_saveEmptyLine();
1112 
1113    /* Editor. */
1114    conf_saveComment("Paths for saving different files from the editor");
1115    conf_saveString("dev_save_sys",conf.dev_save_sys);
1116    conf_saveString("dev_save_map",conf.dev_save_map);
1117    conf_saveString("dev_save_asset",conf.dev_save_asset);
1118    conf_saveEmptyLine();
1119 
1120    /*
1121     * Keybindings.
1122     */
1123    conf_saveEmptyLine();
1124    conf_saveComment("Keybindings");
1125    conf_saveEmptyLine();
1126 
1127    /* Use an extra character in keyname to make sure it's always zero-terminated */
1128    keyname[sizeof(keyname)-1] = '\0';
1129 
1130    /* Iterate over the keybinding names */
1131    for (i=0; strcmp(keybind_info[i][0], "end"); i++) {
1132       /* Save a comment line containing the description */
1133       conf_saveComment(input_getKeybindDescription( keybind_info[i][0] ));
1134 
1135       /* Get the keybind */
1136       key = input_getKeybind( keybind_info[i][0], &type, &mod );
1137 
1138       /* Determine the textual name for the keybind type */
1139       switch (type) {
1140          case KEYBIND_KEYBOARD:  typename = "keyboard";  break;
1141          case KEYBIND_JAXISPOS:  typename = "jaxispos";  break;
1142          case KEYBIND_JAXISNEG:  typename = "jaxisneg";  break;
1143          case KEYBIND_JBUTTON:   typename = "jbutton";   break;
1144          default:                typename = NULL;        break;
1145       }
1146       /* Write a nil if an unknown type */
1147       if ((typename == NULL) || (key == SDLK_UNKNOWN)) {
1148          conf_saveString( keybind_info[i][0],"none");
1149          continue;
1150       }
1151 
1152       /* Determine the textual name for the modifier */
1153       switch ((int)mod) {
1154          case NMOD_CTRL:  modname = "ctrl";   break;
1155          case NMOD_SHIFT: modname = "shift";  break;
1156          case NMOD_ALT:   modname = "alt";    break;
1157          case NMOD_META:  modname = "meta";   break;
1158          case NMOD_ALL:   modname = "any";     break;
1159          default:         modname = "none";    break;
1160       }
1161 
1162       /* Determine the textual name for the key, if a keyboard keybind */
1163       if (type == KEYBIND_KEYBOARD)
1164          quoteLuaString(keyname, sizeof(keyname)-1, SDL_GetKeyName(key));
1165       /* If SDL can't describe the key, store it as an integer */
1166       if (type != KEYBIND_KEYBOARD || strcmp(keyname, "\"unknown key\"") == 0)
1167          nsnprintf(keyname, sizeof(keyname)-1, "%d", key);
1168 
1169       /* Write out a simple Lua table containing the keybind info */
1170       pos += nsnprintf(&buf[pos], sizeof(buf)-pos, "%s = { type = \"%s\", mod = \"%s\", key = %s }\n",
1171             keybind_info[i][0], typename, modname, keyname);
1172    }
1173    conf_saveEmptyLine();
1174 
1175    /* Footer. */
1176    conf_saveComment(GENERATED_END_COMMENT);
1177 
1178    if (old != NULL) {
1179       if (oldfooter != NULL) {
1180          /* oldfooter and oldsize now reference the old content past the footer */
1181          oldsize = MIN((size_t)oldsize, sizeof(buf)-pos);
1182          memcpy(&buf[pos], oldfooter, oldsize);
1183          pos += oldsize;
1184       }
1185       free(old);
1186    }
1187 
1188    if (nfile_writeFile(buf, pos, file) < 0) {
1189       WARN("Failed to write configuration!  You'll most likely have to restore it by copying your backup configuration over your current configuration.");
1190       return -1;
1191    }
1192 
1193    return 0;
1194 }
1195 
1196