1 // options.cxx -- class to handle command line options
2 //
3 // Written by Curtis Olson, started April 1998.
4 //
5 // Copyright (C) 1998  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22 
23 
24 #include <config.h>
25 
26 #include <simgear/compiler.h>
27 #include <simgear/structure/exception.hxx>
28 #include <simgear/debug/logstream.hxx>
29 #include <simgear/timing/sg_time.hxx>
30 #include <simgear/misc/sg_dir.hxx>
31 
32 #include <3rdparty/cjson/cJSON.h>
33 
34 #include <cmath>        // rint()
35 #include <cstdio>
36 #include <cstdlib>      // atof(), atoi()
37 #include <cstring>      // strcmp()
38 #include <algorithm>
39 #include <map>
40 
41 #include <iostream>
42 #include <string>
43 #include <sstream>
44 
45 #include <simgear/math/sg_random.h>
46 #include <simgear/props/props_io.hxx>
47 #include <simgear/io/iostreams/sgstream.hxx>
48 #include <simgear/misc/sg_path.hxx>
49 #include <simgear/misc/sg_dir.hxx>
50 #include <simgear/scene/material/mat.hxx>
51 #include <simgear/sound/soundmgr.hxx>
52 #include <simgear/misc/strutils.hxx>
53 #include <Autopilot/route_mgr.hxx>
54 #include <Aircraft/replay.hxx>
55 #include <Aircraft/initialstate.hxx>
56 
57 #include <GUI/gui.h>
58 #include <GUI/MessageBox.hxx>
59 
60 #if defined(HAVE_QT)
61 #include <GUI/QtLauncher.hxx>
62 #include <GUI/SetupRootDialog.hxx>
63 #endif
64 
65 #include <AIModel/AIManager.hxx>
66 #include <Add-ons/AddonManager.hxx>
67 #include <Main/locale.hxx>
68 #include <Navaids/NavDataCache.hxx>
69 #include "globals.hxx"
70 #include "fg_init.hxx"
71 #include "fg_props.hxx"
72 #include "options.hxx"
73 #include "util.hxx"
74 #include "main.hxx"
75 #include "locale.hxx"
76 #include <Viewer/view.hxx>
77 #include <Viewer/viewmgr.hxx>
78 #include <Environment/presets.hxx>
79 #include <Network/http/httpd.hxx>
80 #include "AircraftDirVisitorBase.hxx"
81 
82 #include <osg/Version>
83 #include <Include/version.h>
84 #include <Include/build.h>
85 #include <simgear/version.h>
86 
87 using std::string;
88 using std::sort;
89 using std::cout;
90 using std::cerr;
91 using std::endl;
92 using std::vector;
93 using std::cin;
94 
95 using namespace flightgear;
96 
97 #define NEW_DEFAULT_MODEL_HZ 120
98 
99 static flightgear::Options* shared_instance = nullptr;
100 
101 static double
atof(const string & str)102 atof( const string& str )
103 {
104     return ::atof( str.c_str() );
105 }
106 
107 static int
atoi(const string & str)108 atoi( const string& str )
109 {
110     return ::atoi( str.c_str() );
111 }
112 
113 static int fgSetupProxy( const char *arg );
114 
115 /**
116  * Set a few fail-safe default property values.
117  *
118  * These should all be set in $FG_ROOT/defaults.xml, but just
119  * in case, we provide some initial sane values here. This method
120  * should be invoked *before* reading any init files.
121  */
fgSetDefaults()122 void fgSetDefaults ()
123 {
124 
125     // Position (deliberately out of range)
126     fgSetDouble("/position/longitude-deg", 9999.0);
127     fgSetDouble("/position/latitude-deg", 9999.0);
128     fgSetDouble("/position/altitude-ft", -9999.0);
129 
130     // Orientation
131     fgSetDouble("/orientation/heading-deg", 9999.0);
132     fgSetDouble("/orientation/roll-deg", 0.0);
133     fgSetDouble("/orientation/pitch-deg", 0.424);
134 
135     // Velocities
136     fgSetDouble("/velocities/uBody-fps", 0.0);
137     fgSetDouble("/velocities/vBody-fps", 0.0);
138     fgSetDouble("/velocities/wBody-fps", 0.0);
139     fgSetDouble("/velocities/speed-north-fps", 0.0);
140     fgSetDouble("/velocities/speed-east-fps", 0.0);
141     fgSetDouble("/velocities/speed-down-fps", 0.0);
142     fgSetDouble("/velocities/airspeed-kt", 0.0);
143     fgSetDouble("/velocities/mach", 0.0);
144 
145     // Presets
146     fgSetDouble("/sim/presets/longitude-deg", 9999.0);
147     fgSetDouble("/sim/presets/latitude-deg", 9999.0);
148     fgSetDouble("/sim/presets/altitude-ft", -9999.0);
149 
150     fgSetDouble("/sim/presets/heading-deg", 9999.0);
151     fgSetDouble("/sim/presets/roll-deg", 0.0);
152     fgSetDouble("/sim/presets/pitch-deg", 0.424);
153 
154     fgSetString("/sim/presets/speed-set", "knots");
155     fgSetDouble("/sim/presets/airspeed-kt", 0.0);
156     fgSetDouble("/sim/presets/mach", 0.0);
157     fgSetDouble("/sim/presets/uBody-fps", 0.0);
158     fgSetDouble("/sim/presets/vBody-fps", 0.0);
159     fgSetDouble("/sim/presets/wBody-fps", 0.0);
160     fgSetDouble("/sim/presets/speed-north-fps", 0.0);
161     fgSetDouble("/sim/presets/speed-east-fps", 0.0);
162     fgSetDouble("/sim/presets/speed-down-fps", 0.0);
163     fgSetDouble("/sim/presets/offset-distance-nm", 0.0);
164 
165     fgSetBool("/sim/presets/runway-requested", false);
166 
167     fgSetBool("/sim/presets/onground", true);
168     fgSetBool("/sim/presets/trim", false);
169 
170     // Miscellaneous
171     fgSetBool("/sim/startup/splash-screen", true);
172     // we want mouse-pointer to have an undefined value if nothing is
173     // specified so we can do the right thing for voodoo-1/2 cards.
174     // fgSetString("/sim/startup/mouse-pointer", "disabled");
175     fgSetBool("/controls/flight/auto-coordination", false);
176     fgSetString("/sim/logging/priority", "alert");
177 
178     // Features
179     fgSetBool("/sim/hud/color/antialiased", false);
180     fgSetBool("/sim/hud/enable3d[1]", true);
181     fgSetBool("/sim/hud/visibility[1]", false);
182     fgSetBool("/sim/panel/visibility", true);
183     fgSetBool("/sim/sound/enabled", true);
184     fgSetBool("/sim/sound/working", true);
185     fgSetBool("/sim/fgcom/enabled", false);
186 
187     // Flight Model options
188     fgSetString("/sim/flight-model", "jsb");
189     fgSetString("/sim/aero", "c172");
190     fgSetInt("/sim/model-hz", NEW_DEFAULT_MODEL_HZ);
191     fgSetDouble("/sim/speed-up", 1.0);
192 
193     // Scenery
194     fgSetString("/sim/scenery/engine", "tilecache");
195 
196     // ( scenery = pagedLOD )
197     fgSetString("/sim/scenery/lod-levels", "1 3 5 7 9");
198     fgSetString("/sim/scenery/lod-res", "1");
199     fgSetString("/sim/scenery/lod-texturing", "bluemarble");
200 
201     // Rendering options
202     fgSetString("/sim/rendering/fog", "nicest");
203     fgSetBool("/environment/clouds/status", true);
204     fgSetBool("/sim/startup/fullscreen", false);
205     fgSetBool("/sim/rendering/shading", true);
206     fgTie( "/sim/rendering/filtering", SGGetTextureFilter, SGSetTextureFilter, false);
207     fgSetInt("/sim/rendering/filtering", 1);
208     fgSetBool("/sim/rendering/wireframe", false);
209     fgSetBool("/sim/rendering/horizon-effect", false);
210     fgSetBool("/sim/rendering/distance-attenuation", false);
211     fgSetBool("/sim/rendering/specular-highlight", true);
212     fgSetString("/sim/rendering/materials-file", "materials.xml");
213     fgSetInt("/sim/startup/xsize", 1024);
214     fgSetInt("/sim/startup/ysize", 768);
215     fgSetInt("/sim/rendering/bits-per-pixel", 32);
216     fgSetString("/sim/view-mode", "pilot");
217     fgSetDouble("/sim/current-view/heading-offset-deg", 0);
218 
219     // HUD options
220     fgSetString("/sim/startup/units", "feet");
221     fgSetString("/sim/hud/frame-stat-type", "tris");
222 
223     // Time options
224     fgSetInt("/sim/startup/time-offset", 0);
225     fgSetString("/sim/startup/time-offset-type", "system-offset");
226     fgSetLong("/sim/time/cur-time-override", 0);
227 
228     // Freeze options
229     fgSetBool("/sim/freeze/master", false);
230     fgSetBool("/sim/freeze/position", false);
231     fgSetBool("/sim/freeze/clock", false);
232     fgSetBool("/sim/freeze/fuel", false);
233 
234     fgSetString("/sim/multiplay/callsign", "callsign");
235     fgSetString("/sim/multiplay/rxhost", "");
236     fgSetString("/sim/multiplay/txhost", "");
237     fgSetInt("/sim/multiplay/rxport", 0);
238     fgSetInt("/sim/multiplay/txport", 0);
239 
240     SGPropertyNode* v = globals->get_props()->getNode("/sim/version", true);
241     v->setValueReadOnly("flightgear", FLIGHTGEAR_VERSION);
242     v->setValueReadOnly("simgear", SG_STRINGIZE(SIMGEAR_VERSION));
243     v->setValueReadOnly("openscenegraph", osgGetVersion());
244 #if OSG_VERSION_LESS_THAN(3,5,2)
245     v->setValueReadOnly("openscenegraph-thread-safe-reference-counting",
246                          osg::Referenced::getThreadSafeReferenceCounting());
247 #endif
248     v->setValueReadOnly("revision", REVISION);
249     v->setValueReadOnly("build-number", JENKINS_BUILD_NUMBER);
250     v->setValueReadOnly("build-id", JENKINS_BUILD_ID);
251     v->setValueReadOnly("hla-support", bool(FG_HAVE_HLA));
252     v->setValueReadOnly("build-type", FG_BUILD_TYPE);
253 #ifdef ENABLE_COMPOSITOR
254     static constexpr bool compositor_support = true;
255 #else
256     static constexpr bool compositor_support = false;
257 #endif
258     v->setValueReadOnly("compositor-support", compositor_support);
259 
260     char* envp = ::getenv( "http_proxy" );
261     if( envp != nullptr )
262       fgSetupProxy( envp );
263 }
264 
265 ///////////////////////////////////////////////////////////////////////////////
266 // helper object to implement the --show-aircraft command.
267 // resides here so we can share the fgFindAircraftInDir template above,
268 // and hence ensure this command lists exectly the same aircraft as the normal
269 // loading path.
270 class ShowAircraft : public AircraftDirVistorBase
271 {
272 public:
ShowAircraft()273     ShowAircraft()
274     {
275         _minStatus = getNumMaturity(fgGetString("/sim/aircraft-min-status", "all"));
276     }
277 
278 
show(const vector<SGPath> & path_list)279     void show(const vector<SGPath> & path_list)
280     {
281 		for (vector<SGPath>::const_iterator p = path_list.begin();
282 			 p != path_list.end(); ++p)
283 			visitDir(*p, 0);
284 
285         simgear::requestConsole(); // ensure console is shown on Windows
286 
287         std::sort(_aircraft.begin(), _aircraft.end(), ciLessLibC());
288         cout << "Available aircraft:" << endl;
289         for ( unsigned int i = 0; i < _aircraft.size(); i++ ) {
290             cout << _aircraft[i] << endl;
291         }
292     }
293 
294 private:
visit(const SGPath & path)295     virtual VisitResult visit(const SGPath& path)
296     {
297         SGPropertyNode root;
298         try {
299             readProperties(path, &root);
300         } catch (sg_exception& ) {
301             return VISIT_CONTINUE;
302         }
303 
304         int maturity = 0;
305         string descStr("   ");
306         descStr += path.file();
307         // trim common suffix from file names
308         int nPos = descStr.rfind("-set.xml");
309         if (nPos == (int)(descStr.size() - 8)) {
310             descStr.resize(nPos);
311         }
312 
313         SGPropertyNode *node = root.getNode("sim");
314         if (node) {
315             SGPropertyNode* desc = node->getNode("description");
316             // if a status tag is found, read it in
317             if (node->hasValue("status")) {
318                 maturity = getNumMaturity(node->getStringValue("status"));
319             }
320 
321             if (desc) {
322                 if (descStr.size() <= 27+3) {
323                     descStr.append(29+3-descStr.size(), ' ');
324                 } else {
325                     descStr += '\n';
326                     descStr.append( 32, ' ');
327                 }
328                 descStr += desc->getStringValue();
329             }
330         } // of have 'sim' node
331 
332         if (maturity >= _minStatus) {
333             _aircraft.push_back(descStr);
334         }
335 
336         return VISIT_CONTINUE;
337     }
338 
339 
getNumMaturity(const char * str)340     int getNumMaturity(const char * str)
341     {
342         // Changes should also be reflected in $FG_ROOT/options.xml
343         const char* levels[] = {"alpha","beta","early-production","production"};
344 
345         if (!strcmp(str, "all")) {
346             return 0;
347         }
348 
349         for (size_t i=0; i<(sizeof(levels)/sizeof(levels[0]));i++)
350             if (strcmp(str,levels[i])==0)
351                 return i;
352 
353         return 0;
354     }
355 
356     // recommended in Meyers, Effective STL when internationalization and embedded
357     // NULLs aren't an issue.  Much faster than the STL or Boost lex versions.
358     struct ciLessLibC : public std::binary_function<string, string, bool>
359     {
operator ()ShowAircraft::ciLessLibC360         bool operator()(const std::string &lhs, const std::string &rhs) const
361         {
362             return strcasecmp(lhs.c_str(), rhs.c_str()) < 0 ? 1 : 0;
363         }
364     };
365 
366     int _minStatus;
367     string_list _aircraft;
368 };
369 
370 /*
371  * Search in the current directory, and in on directory deeper
372  * for <aircraft>-set.xml configuration files and show the aircaft name
373  * and the contents of the<description> tag in a sorted manner.
374  *
375  * @parampath the directory to search for configuration files
376  */
fgShowAircraft(const vector<SGPath> & path_list)377 void fgShowAircraft(const vector<SGPath> &path_list)
378 {
379     ShowAircraft s;
380     s.show(path_list);
381 
382 #ifdef _MSC_VER
383     cout << "Hit a key to continue..." << endl;
384     std::cin.get();
385 #endif
386 }
387 
388 
389 static bool
parse_wind(const string & wind,double * min_hdg,double * max_hdg,double * speed,double * gust)390 parse_wind (const string &wind, double * min_hdg, double * max_hdg,
391 	    double * speed, double * gust)
392 {
393   string::size_type pos = wind.find('@');
394   if (pos == string::npos)
395     return false;
396   string dir = wind.substr(0, pos);
397   string spd = wind.substr(pos+1);
398   pos = dir.find(':');
399   if (pos == string::npos) {
400     *min_hdg = *max_hdg = atof(dir.c_str());
401   } else {
402     *min_hdg = atof(dir.substr(0,pos).c_str());
403     *max_hdg = atof(dir.substr(pos+1).c_str());
404   }
405   pos = spd.find(':');
406   if (pos == string::npos) {
407     *speed = *gust = atof(spd.c_str());
408   } else {
409     *speed = atof(spd.substr(0,pos).c_str());
410     *gust = atof(spd.substr(pos+1).c_str());
411   }
412   return true;
413 }
414 
415 static bool
parseIntValue(char ** ppParserPos,int * pValue,int min,int max,const char * field,const char * argument)416 parseIntValue(char** ppParserPos, int* pValue,int min, int max, const char* field, const char* argument)
417 {
418     if ( !strlen(*ppParserPos) )
419         return true;
420 
421     char num[256];
422     int i = 0;
423 
424     while ( isdigit((*ppParserPos)[0]) && (i<255) )
425     {
426         num[i] = (*ppParserPos)[0];
427         (*ppParserPos)++;
428         i++;
429     }
430     num[i] = '\0';
431 
432     switch ((*ppParserPos)[0])
433     {
434         case 0:
435             break;
436         case ':':
437             (*ppParserPos)++;
438             break;
439         default:
440             SG_LOG(SG_GENERAL, SG_ALERT, "Illegal character in time string for " << field << ": '" <<
441                     (*ppParserPos)[0] << "'.");
442             // invalid field - skip rest of string to avoid further errors
443             while ((*ppParserPos)[0])
444                 (*ppParserPos)++;
445             return false;
446     }
447 
448     if (i<=0)
449         return true;
450 
451     int value = atoi(num);
452     if ((value < min)||(value > max))
453     {
454         SG_LOG(SG_GENERAL, SG_ALERT, "Invalid " << field << " in '" << argument <<
455                "'. Valid range is " << min << "-" << max << ".");
456         return false;
457     }
458     else
459     {
460         *pValue = value;
461         return true;
462     }
463 }
464 
465 // parse a time string ([+/-]%f[:%f[:%f]]) into hours
466 static double
parse_time(const string & time_in)467 parse_time(const string& time_in) {
468     char *time_str, num[256];
469     double hours, minutes, seconds;
470     double result = 0.0;
471     int sign = 1;
472     int i;
473 
474     time_str = (char *)time_in.c_str();
475 
476     // printf("parse_time(): %s\n", time_str);
477 
478     // check for sign
479     if ( strlen(time_str) ) {
480 	if ( time_str[0] == '+' ) {
481 	    sign = 1;
482 	    time_str++;
483 	} else if ( time_str[0] == '-' ) {
484 	    sign = -1;
485 	    time_str++;
486 	}
487     }
488     // printf("sign = %d\n", sign);
489 
490     // get hours
491     if ( strlen(time_str) ) {
492 	i = 0;
493 	while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
494 	    num[i] = time_str[0];
495 	    time_str++;
496 	    i++;
497 	}
498 	if ( time_str[0] == ':' ) {
499 	    time_str++;
500 	}
501 	num[i] = '\0';
502 	hours = atof(num);
503 	// printf("hours = %.2lf\n", hours);
504 
505 	result += hours;
506     }
507 
508     // get minutes
509     if ( strlen(time_str) ) {
510 	i = 0;
511 	while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
512 	    num[i] = time_str[0];
513 	    time_str++;
514 	    i++;
515 	}
516 	if ( time_str[0] == ':' ) {
517 	    time_str++;
518 	}
519 	num[i] = '\0';
520 	minutes = atof(num);
521 	// printf("minutes = %.2lf\n", minutes);
522 
523 	result += minutes / 60.0;
524     }
525 
526     // get seconds
527     if ( strlen(time_str) ) {
528 	i = 0;
529 	while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
530 	    num[i] = time_str[0];
531 	    time_str++;
532 	    i++;
533 	}
534 	num[i] = '\0';
535 	seconds = atof(num);
536 	// printf("seconds = %.2lf\n", seconds);
537 
538 	result += seconds / 3600.0;
539     }
540 
541     SG_LOG( SG_GENERAL, SG_INFO, " parse_time() = " << sign * result );
542 
543     return(sign * result);
544 }
545 
546 // parse a date string (yyyy:mm:dd:hh:mm:ss) into a time_t (seconds)
547 static long int
parse_date(const string & date,const char * timeType)548 parse_date( const string& date, const char* timeType)
549 {
550     struct tm gmt,*pCurrentTime;
551     int year,month,day,hour,minute,second;
552     char *argument, *date_str;
553 
554     SGTime CurrentTime;
555     CurrentTime.update(SGGeod(),0,0);
556 
557     // FIXME This should obtain system/aircraft/GMT time depending on timeType
558     pCurrentTime = CurrentTime.getGmt();
559 
560     // initialize all fields with current time
561     year   = pCurrentTime->tm_year + 1900;
562     month  = pCurrentTime->tm_mon + 1;
563     day    = pCurrentTime->tm_mday;
564     hour   = pCurrentTime->tm_hour;
565     minute = pCurrentTime->tm_min;
566     second = pCurrentTime->tm_sec;
567 
568     argument = (char *)date.c_str();
569     date_str = argument;
570 
571     // start with parsing year
572     if (!strlen(date_str) ||
573         !parseIntValue(&date_str,&year,0,9999,"year",argument))
574     {
575         return -1;
576     }
577 
578     if (year < 1970)
579     {
580         SG_LOG(SG_GENERAL, SG_ALERT, "Invalid year '" << year << "'. Use 1970 or later.");
581         return -1;
582     }
583 
584     parseIntValue(&date_str, &month,  1, 12, "month",  argument);
585     parseIntValue(&date_str, &day,    1, 31, "day",    argument);
586     parseIntValue(&date_str, &hour,   0, 23, "hour",   argument);
587     parseIntValue(&date_str, &minute, 0, 59, "minute", argument);
588     parseIntValue(&date_str, &second, 0, 59, "second", argument);
589 
590     gmt.tm_sec  = second;
591     gmt.tm_min  = minute;
592     gmt.tm_hour = hour;
593     gmt.tm_mday = day;
594     gmt.tm_mon  = month - 1;
595     gmt.tm_year = year -1900;
596     gmt.tm_isdst = 0; // ignore daylight savings time for the moment
597 
598     time_t theTime = sgTimeGetGMT( gmt.tm_year, gmt.tm_mon, gmt.tm_mday,
599                                    gmt.tm_hour, gmt.tm_min, gmt.tm_sec );
600 
601     SG_LOG(SG_GENERAL, SG_INFO, "Configuring startup time to " << ctime(&theTime));
602 
603     return (theTime);
604 }
605 
606 
607 // parse angle in the form of [+/-]ddd:mm:ss into degrees
608 static double
parse_degree(const string & degree_str)609 parse_degree( const string& degree_str) {
610     double result = parse_time( degree_str );
611 
612     // printf("Degree = %.4f\n", result);
613 
614     return(result);
615 }
616 
617 
618 // parse time offset string into seconds
619 static long int
parse_time_offset(const string & time_str)620 parse_time_offset( const string& time_str) {
621    long int result;
622 
623     // printf("time offset = %s\n", time_str);
624 
625 #ifdef HAVE_RINT
626     result = (int)rint(parse_time(time_str) * 3600.0);
627 #else
628     result = (int)(parse_time(time_str) * 3600.0);
629 #endif
630 
631     // printf("parse_time_offset(): %d\n", result);
632 
633     return( result );
634 }
635 
636 
637 // Parse --fov=x.xx type option
638 static double
parse_fov(const string & arg)639 parse_fov( const string& arg ) {
640     double fov = atof(arg);
641 
642     if ( fov < FG_FOV_MIN ) { fov = FG_FOV_MIN; }
643     if ( fov > FG_FOV_MAX ) { fov = FG_FOV_MAX; }
644 
645     fgSetDouble("/sim/view[0]/config/default-field-of-view-deg", fov);
646 
647     // printf("parse_fov(): result = %.4f\n", fov);
648 
649     return fov;
650 }
651 
652 
653 // Parse I/O channel option
654 //
655 // Format is "--protocol=medium,direction,hz,medium_options,..."
656 //
657 //   protocol = { native, nmea, garmin, AV400, AV400Sim, fgfs, rul, pve, etc. }
658 //   medium = { serial, socket, file, etc. }
659 //   direction = { in, out, bi }
660 //   hz = number of times to process channel per second (floating
661 //        point values are ok.
662 //
663 // Serial example "--nmea=serial,dir,hz,device,baud" where
664 //
665 //  device = OS device name of serial line to be open()'ed
666 //  baud = {300, 1200, 2400, ..., 230400}
667 //
668 // Socket example "--native=socket,dir,hz,machine,port,style" where
669 //
670 //  machine = machine name or ip address if client (leave empty if server)
671 //  port = port, leave empty to let system choose
672 //  style = tcp or udp
673 //
674 // File example "--garmin=file,dir,hz,filename" where
675 //
676 //  filename = file system file name
677 
678 static bool
add_channel(const string & type,const string & channel_str)679 add_channel( const string& type, const string& channel_str ) {
680     // This check is necessary to prevent fgviewer from segfaulting when given
681     // weird options. (It doesn't run the full initialization)
682     if(!globals->get_channel_options_list())
683     {
684         SG_LOG(SG_GENERAL, SG_ALERT, "Option " << type << "=" << channel_str
685                                      << " ignored.");
686         return false;
687     }
688     SG_LOG(SG_GENERAL, SG_INFO, "Channel string = " << channel_str );
689     globals->get_channel_options_list()->push_back( type + "," + channel_str );
690     return true;
691 }
692 
693 static void
clearLocation()694 clearLocation ()
695 {
696     fgSetString("/sim/presets/airport-id", "");
697     fgSetString("/sim/presets/vor-id", "");
698     fgSetString("/sim/presets/ndb-id", "");
699     fgSetString("/sim/presets/carrier", "");
700     fgSetString("/sim/presets/parkpos", "");
701     fgSetString("/sim/presets/carrier-position", "");
702     fgSetString("/sim/presets/fix", "");
703 }
704 
705 /*
706  Using --addon=/foo/bar does:
707    - register the add-on with the AddonManager (enabling, among other things,
708      add-on-specific resources for simgear::ResourceManager);
709    - load /foo/bar/addon-config.xml into the Global Property Tree;
710    - add /foo/bar to the list of aircraft paths to provide read access:
711    - set various properties related to the add-on under /addons;
712    - load /foo/bar/addon-main.nas into namespace __addon[ADDON_ID]__
713      (see $FG_ROOT/Nasal/addons.nas);
714    - call the main() function defined in that file.
715 
716  For more details, see $FG_ROOT/Docs/README.add-ons.
717 */
718 static int
fgOptAddon(const char * arg)719 fgOptAddon(const char *arg)
720 {
721   const SGPath addonPath = SGPath::fromUtf8(arg);
722   const auto& addonManager = addons::AddonManager::instance();
723 
724   try {
725     addonManager->registerAddon(addonPath);
726   } catch (const sg_exception &e) {
727     string msg = "Error registering an add-on: " + e.getFormattedMessage();
728     SG_LOG(SG_GENERAL, SG_ALERT, msg);
729     flightgear::fatalMessageBoxThenExit(
730       "FlightGear", "Unable to register an add-on.", msg);
731   }
732 
733   return FG_OPTIONS_OK;
734 }
735 
736 static int
fgOptAdditionalDataDir(const char * arg)737 fgOptAdditionalDataDir(const char* arg)
738 {
739     const SGPath dataPath = SGPath::fromUtf8(arg);
740     if (!dataPath.exists()) {
741         SG_LOG(SG_GENERAL, SG_ALERT, "--data path not found:'" << dataPath << "'");
742         flightgear::fatalMessageBoxWithoutExit(
743             "FlightGear", "Data path not found: '" + dataPath.utf8Str() + "'.");
744         return FG_OPTIONS_EXIT;
745     }
746 
747     globals->append_data_path(dataPath, false /* = before FG_ROOT */);
748     return FG_OPTIONS_OK;
749 }
750 
751 static int
fgOptVOR(const char * arg)752 fgOptVOR( const char * arg )
753 {
754     clearLocation();
755     fgSetString("/sim/presets/vor-id", arg);
756     return FG_OPTIONS_OK;
757 }
758 
759 static int
fgOptNDB(const char * arg)760 fgOptNDB( const char * arg )
761 {
762     clearLocation();
763     fgSetString("/sim/presets/ndb-id", arg);
764     return FG_OPTIONS_OK;
765 }
766 
767 static int
fgOptCarrier(const char * arg)768 fgOptCarrier( const char * arg )
769 {
770     clearLocation();
771     fgSetString("/sim/presets/carrier", arg);
772     return FG_OPTIONS_OK;
773 }
774 
775 static int
fgOptCarrierPos(const char * arg)776 fgOptCarrierPos( const char * arg )
777 {
778     fgSetString("/sim/presets/carrier-position", arg);
779     return FG_OPTIONS_OK;
780 }
781 
782 static int
fgOptParkpos(const char * arg)783 fgOptParkpos( const char * arg )
784 {
785     fgSetString("/sim/presets/parkpos", arg);
786     fgSetBool("/sim/presets/parking-requested", true);
787     return FG_OPTIONS_OK;
788 }
789 
790 static int
fgOptFIX(const char * arg)791 fgOptFIX( const char * arg )
792 {
793     clearLocation();
794     fgSetString("/sim/presets/fix", arg);
795     return FG_OPTIONS_OK;
796 }
797 
798 static int
fgOptLon(const char * arg)799 fgOptLon( const char *arg )
800 {
801     clearLocation();
802     fgSetDouble("/sim/presets/longitude-deg", parse_degree( arg ));
803     fgSetDouble("/position/longitude-deg", parse_degree( arg ));
804     return FG_OPTIONS_OK;
805 }
806 
807 static int
fgOptLat(const char * arg)808 fgOptLat( const char *arg )
809 {
810     clearLocation();
811     fgSetDouble("/sim/presets/latitude-deg", parse_degree( arg ));
812     fgSetDouble("/position/latitude-deg", parse_degree( arg ));
813     return FG_OPTIONS_OK;
814 }
815 
816 static int
fgOptAltitude(const char * arg)817 fgOptAltitude( const char *arg )
818 {
819     fgSetBool("/sim/presets/onground", false);
820     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
821         fgSetDouble("/sim/presets/altitude-ft", atof( arg ));
822     else
823         fgSetDouble("/sim/presets/altitude-ft",
824 		    atof( arg ) * SG_METER_TO_FEET);
825     return FG_OPTIONS_OK;
826 }
827 
828 static int
fgOptUBody(const char * arg)829 fgOptUBody( const char *arg )
830 {
831     fgSetString("/sim/presets/speed-set", "UVW");
832     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
833 	fgSetDouble("/sim/presets/uBody-fps", atof( arg ));
834     else
835 	fgSetDouble("/sim/presets/uBody-fps",
836                     atof( arg ) * SG_METER_TO_FEET);
837     return FG_OPTIONS_OK;
838 }
839 
840 static int
fgOptVBody(const char * arg)841 fgOptVBody( const char *arg )
842 {
843     fgSetString("/sim/presets/speed-set", "UVW");
844     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
845 	fgSetDouble("/sim/presets/vBody-fps", atof( arg ));
846     else
847 	fgSetDouble("/sim/presets/vBody-fps",
848 			    atof( arg ) * SG_METER_TO_FEET);
849     return FG_OPTIONS_OK;
850 }
851 
852 static int
fgOptWBody(const char * arg)853 fgOptWBody( const char *arg )
854 {
855     fgSetString("/sim/presets/speed-set", "UVW");
856     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
857 	fgSetDouble("/sim/presets/wBody-fps", atof(arg));
858     else
859 	fgSetDouble("/sim/presets/wBody-fps",
860 			    atof(arg) * SG_METER_TO_FEET);
861     return FG_OPTIONS_OK;
862 }
863 
864 static int
fgOptVNorth(const char * arg)865 fgOptVNorth( const char *arg )
866 {
867     fgSetString("/sim/presets/speed-set", "NED");
868     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
869 	fgSetDouble("/sim/presets/speed-north-fps", atof( arg ));
870     else
871 	fgSetDouble("/sim/presets/speed-north-fps",
872 			    atof( arg ) * SG_METER_TO_FEET);
873     return FG_OPTIONS_OK;
874 }
875 
876 static int
fgOptVEast(const char * arg)877 fgOptVEast( const char *arg )
878 {
879     fgSetString("/sim/presets/speed-set", "NED");
880     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
881 	fgSetDouble("/sim/presets/speed-east-fps", atof(arg));
882     else
883 	fgSetDouble("/sim/presets/speed-east-fps",
884 		    atof(arg) * SG_METER_TO_FEET);
885     return FG_OPTIONS_OK;
886 }
887 
888 static int
fgOptVDown(const char * arg)889 fgOptVDown( const char *arg )
890 {
891     fgSetString("/sim/presets/speed-set", "NED");
892     if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
893 	fgSetDouble("/sim/presets/speed-down-fps", atof(arg));
894     else
895 	fgSetDouble("/sim/presets/speed-down-fps",
896 			    atof(arg) * SG_METER_TO_FEET);
897     return FG_OPTIONS_OK;
898 }
899 
900 static int
fgOptVc(const char * arg)901 fgOptVc( const char *arg )
902 {
903     // fgSetString("/sim/presets/speed-set", "knots");
904     // fgSetDouble("/velocities/airspeed-kt", atof(arg.substr(5)));
905     fgSetString("/sim/presets/speed-set", "knots");
906     fgSetDouble("/sim/presets/airspeed-kt", atof(arg));
907     return FG_OPTIONS_OK;
908 }
909 
910 static int
fgOptMach(const char * arg)911 fgOptMach( const char *arg )
912 {
913     fgSetString("/sim/presets/speed-set", "mach");
914     fgSetDouble("/sim/presets/mach", atof(arg));
915     return FG_OPTIONS_OK;
916 }
917 
918 static int
fgOptRoc(const char * arg)919 fgOptRoc( const char *arg )
920 {
921     fgSetDouble("/sim/presets/vertical-speed-fps", atof(arg)/60);
922     return FG_OPTIONS_OK;
923 }
924 
925 static int
fgOptFgScenery(const char * arg)926 fgOptFgScenery( const char *arg )
927 {
928     globals->append_fg_scenery(SGPath::pathsFromUtf8(arg));
929     return FG_OPTIONS_OK;
930 }
931 
932 static int
fgOptEnhancedLighting(const char * arg)933 fgOptEnhancedLighting( const char *arg )
934 {
935     SG_LOG(SG_ALL,SG_ALERT,
936            "the options --enable-enhanced-lighting and "
937            "--disable-enhanced-lighting are no longer supported and will be "
938            "removed in a future FlightGear version! Please do not use them");
939     return FG_OPTIONS_EXIT;
940 }
941 
942 static int
fgOptAllowNasalRead(const char * arg)943 fgOptAllowNasalRead( const char *arg )
944 {
945     PathList paths = SGPath::pathsFromUtf8(arg);
946     if(paths.size() == 0) {
947         SG_LOG(SG_GENERAL, SG_WARN, "--allow-nasal-read requires a list of directories to allow");
948     }
949     for( PathList::const_iterator it = paths.begin(); it != paths.end(); ++it ) {
950         globals->append_read_allowed_paths(*it);
951     }
952     return FG_OPTIONS_OK;
953 }
954 
955 static int
fgOptFov(const char * arg)956 fgOptFov( const char *arg )
957 {
958     parse_fov( arg );
959     return FG_OPTIONS_OK;
960 }
961 
962 static int
fgOptGeometry(const char * arg)963 fgOptGeometry( const char *arg )
964 {
965     bool geometry_ok = true;
966     int xsize = 0, ysize = 0;
967     string geometry = arg;
968     string::size_type i = geometry.find('x');
969 
970     if (i != string::npos) {
971         xsize = atoi(geometry.substr(0, i));
972         ysize = atoi(geometry.substr(i+1));
973     } else {
974         geometry_ok = false;
975     }
976 
977     if ( xsize <= 0 || ysize <= 0 ) {
978         xsize = 640;
979         ysize = 480;
980         geometry_ok = false;
981     }
982 
983     if ( !geometry_ok ) {
984         SG_LOG( SG_GENERAL, SG_ALERT, "Unknown geometry: " << geometry );
985         SG_LOG( SG_GENERAL, SG_ALERT,
986  	        "Setting geometry to " << xsize << 'x' << ysize << '\n');
987     } else {
988         SG_LOG( SG_GENERAL, SG_INFO,
989 	        "Setting geometry to " << xsize << 'x' << ysize << '\n');
990         fgSetInt("/sim/startup/xsize", xsize);
991         fgSetInt("/sim/startup/ysize", ysize);
992     }
993     return FG_OPTIONS_OK;
994 }
995 
996 static int
fgOptBpp(const char * arg)997 fgOptBpp( const char *arg )
998 {
999     string bits_per_pix = arg;
1000     if ( bits_per_pix == "16" ) {
1001 	fgSetInt("/sim/rendering/bits-per-pixel", 16);
1002     } else if ( bits_per_pix == "24" ) {
1003 	fgSetInt("/sim/rendering/bits-per-pixel", 24);
1004     } else if ( bits_per_pix == "32" ) {
1005 	fgSetInt("/sim/rendering/bits-per-pixel", 32);
1006     } else {
1007 	SG_LOG(SG_GENERAL, SG_ALERT, "Unsupported bpp " << bits_per_pix);
1008     }
1009     return FG_OPTIONS_OK;
1010 }
1011 
1012 static int
fgOptTimeOffset(const char * arg)1013 fgOptTimeOffset( const char *arg )
1014 {
1015     fgSetLong("/sim/startup/time-offset",
1016                 parse_time_offset( arg ));
1017     fgSetString("/sim/startup/time-offset-type", "system-offset");
1018     return FG_OPTIONS_OK;
1019 }
1020 
1021 static int
fgOptStartDateSys(const char * arg)1022 fgOptStartDateSys( const char *arg )
1023 {
1024     long int theTime = parse_date( arg, "system" );
1025     if (theTime>=0)
1026     {
1027         fgSetLong("/sim/startup/time-offset",  theTime);
1028         fgSetString("/sim/startup/time-offset-type", "system");
1029     }
1030     return FG_OPTIONS_OK;
1031 }
1032 
1033 static int
fgOptStartDateLat(const char * arg)1034 fgOptStartDateLat( const char *arg )
1035 {
1036     long int theTime = parse_date( arg, "latitude" );
1037     if (theTime>=0)
1038     {
1039         fgSetLong("/sim/startup/time-offset", theTime);
1040         fgSetString("/sim/startup/time-offset-type", "latitude");
1041     }
1042     return FG_OPTIONS_OK;
1043 }
1044 
1045 static int
fgOptStartDateGmt(const char * arg)1046 fgOptStartDateGmt( const char *arg )
1047 {
1048     long int theTime = parse_date( arg, "gmt" );
1049     if (theTime>=0)
1050     {
1051         fgSetLong("/sim/startup/time-offset", theTime);
1052         fgSetString("/sim/startup/time-offset-type", "gmt");
1053     }
1054     return FG_OPTIONS_OK;
1055 }
1056 
1057 static int
fgOptJpgHttpd(const char * arg)1058 fgOptJpgHttpd( const char * arg )
1059 {
1060   SG_LOG(SG_ALL,SG_ALERT,
1061    "the option --jpg-httpd is no longer supported! Please use --httpd instead."
1062    " URL for the screenshot within the new httpd is http://YourFgServer:xxxx/screenshot");
1063   return FG_OPTIONS_EXIT;
1064 }
1065 
1066 static int
fgOptHttpd(const char * arg)1067 fgOptHttpd( const char * arg )
1068 {
1069     // port may be any valid address:port notation
1070     // like 127.0.0.1:8080
1071     // or just the port 8080
1072     string port = simgear::strutils::strip(string(arg));
1073     if( port.empty() ) return FG_OPTIONS_ERROR;
1074     fgSetString( string(flightgear::http::PROPERTY_ROOT).append("/options/listening-port").c_str(), port );
1075     return FG_OPTIONS_OK;
1076 }
1077 
1078 static int
fgSetupProxy(const char * arg)1079 fgSetupProxy( const char *arg )
1080 {
1081     string options = simgear::strutils::strip( arg );
1082     string host, port, auth;
1083     string::size_type pos;
1084 
1085     // this is NURLP - NURLP is not an url parser
1086     if( simgear::strutils::starts_with( options, "http://" ) )
1087         options = options.substr( 7 );
1088     if( simgear::strutils::ends_with( options, "/" ) )
1089         options = options.substr( 0, options.length() - 1 );
1090 
1091     host = port = auth = "";
1092     if ((pos = options.find("@")) != string::npos)
1093         auth = options.substr(0, pos++);
1094     else
1095         pos = 0;
1096 
1097     host = options.substr(pos, options.size());
1098     if ((pos = host.find(":")) != string::npos) {
1099         port = host.substr(++pos, host.size());
1100         host.erase(--pos, host.size());
1101     }
1102 
1103     fgSetString("/sim/presets/proxy/host", host.c_str());
1104     fgSetString("/sim/presets/proxy/port", port.c_str());
1105     fgSetString("/sim/presets/proxy/authentication", auth.c_str());
1106 
1107     return FG_OPTIONS_OK;
1108 }
1109 
1110 static int
fgOptTraceRead(const char * arg)1111 fgOptTraceRead( const char *arg )
1112 {
1113     string name = arg;
1114     SG_LOG(SG_GENERAL, SG_INFO, "Tracing reads for property " << name);
1115     fgGetNode(name.c_str(), true)
1116 	->setAttribute(SGPropertyNode::TRACE_READ, true);
1117     return FG_OPTIONS_OK;
1118 }
1119 
1120 static int
fgOptLogLevel(const char * arg)1121 fgOptLogLevel( const char *arg )
1122 {
1123     fgSetString("/sim/logging/priority", arg);
1124     setLoggingPriority(arg);
1125 
1126     return FG_OPTIONS_OK;
1127 }
1128 
1129 static int
fgOptLogClasses(const char * arg)1130 fgOptLogClasses( const char *arg )
1131 {
1132     fgSetString("/sim/logging/classes", arg);
1133     setLoggingClasses (arg);
1134 
1135     return FG_OPTIONS_OK;
1136 }
1137 
1138 static int
fgOptLogDir(const char * arg)1139 fgOptLogDir(const char* arg)
1140 {
1141     SGPath dirPath;
1142     if (!strcmp(arg, "desktop")) {
1143         dirPath = SGPath::desktop();
1144     } else {
1145         dirPath = SGPath::fromUtf8(arg);
1146     }
1147 
1148     if (!dirPath.isDir()) {
1149         SG_LOG(SG_GENERAL, SG_ALERT, "cannot find logging location " << dirPath);
1150         return FG_OPTIONS_ERROR;
1151     }
1152 
1153     if (!dirPath.canWrite()) {
1154         SG_LOG(SG_GENERAL, SG_ALERT, "cannot write to logging location " << dirPath);
1155         return FG_OPTIONS_ERROR;
1156     }
1157 
1158     // generate the log file name
1159     SGPath logFile;
1160     {
1161         char fileNameBuffer[100];
1162         time_t now;
1163         time(&now);
1164         strftime(fileNameBuffer, 99, "FlightGear_%F", localtime(&now));
1165 
1166         unsigned int logsTodayCount = 0;
1167         while (true) {
1168             std::ostringstream os;
1169             os << fileNameBuffer << "_" << logsTodayCount++ << ".log";
1170             logFile = dirPath / os.str();
1171             if (!logFile.exists()) {
1172                 break;
1173             }
1174         }
1175     }
1176 
1177     sglog().logToFile(logFile, sglog().get_log_classes(), sglog().get_log_priority());
1178 
1179     return FG_OPTIONS_OK;
1180 }
1181 
1182 static int
fgOptTraceWrite(const char * arg)1183 fgOptTraceWrite( const char *arg )
1184 {
1185     string name = arg;
1186     SG_LOG(SG_GENERAL, SG_INFO, "Tracing writes for property " << name);
1187     fgGetNode(name.c_str(), true)
1188 	->setAttribute(SGPropertyNode::TRACE_WRITE, true);
1189     return FG_OPTIONS_OK;
1190 }
1191 
1192 static int
fgOptViewOffset(const char * arg)1193 fgOptViewOffset( const char *arg )
1194 {
1195     // $$$ begin - added VS Renganathan, 14 Oct 2K
1196     // for multi-window outside window imagery
1197     string woffset = arg;
1198     double default_view_offset = 0.0;
1199     if ( woffset == "LEFT" ) {
1200 	    default_view_offset = SGD_PI * 0.25;
1201     } else if ( woffset == "RIGHT" ) {
1202 	default_view_offset = SGD_PI * 1.75;
1203     } else if ( woffset == "CENTER" ) {
1204 	default_view_offset = 0.00;
1205     } else {
1206 	default_view_offset = atof( woffset.c_str() ) * SGD_DEGREES_TO_RADIANS;
1207     }
1208     /* apparently not used (CLO, 11 Jun 2002)
1209         FGViewer *pilot_view =
1210 	    (FGViewer *)globals->get_viewmgr()->get_view( 0 ); */
1211     // this will work without calls to the viewer...
1212     fgSetDouble( "/sim/current-view/heading-offset-deg",
1213                     default_view_offset  * SGD_RADIANS_TO_DEGREES );
1214     // $$$ end - added VS Renganathan, 14 Oct 2K
1215     return FG_OPTIONS_OK;
1216 }
1217 
1218 static int
fgOptVisibilityMeters(const char * arg)1219 fgOptVisibilityMeters( const char *arg )
1220 {
1221     Environment::Presets::VisibilitySingleton::instance()->preset( atof( arg ) );
1222     return FG_OPTIONS_OK;
1223 }
1224 
1225 static int
fgOptVisibilityMiles(const char * arg)1226 fgOptVisibilityMiles( const char *arg )
1227 {
1228     Environment::Presets::VisibilitySingleton::instance()->preset( atof( arg ) * 5280.0 * SG_FEET_TO_METER );
1229     return FG_OPTIONS_OK;
1230 }
1231 
1232 static int
fgOptMetar(const char * arg)1233 fgOptMetar(const char *arg)
1234 {
1235     // The given METAR string cannot be effective without disabling
1236     // real weather fetching.
1237     fgSetBool("/environment/realwx/enabled", false);
1238     // The user-supplied METAR string
1239     fgSetString("/environment/metar/data", arg);
1240 
1241     return FG_OPTIONS_OK;
1242 }
1243 static int
fgOptConsole(const char * arg)1244 fgOptConsole(const char *arg)
1245 {
1246     static bool already_done = false;
1247     if (!already_done)
1248     {
1249         already_done = true;
1250         simgear::requestConsole();
1251     }
1252     return FG_OPTIONS_OK;
1253 }
1254 
1255 static int
fgOptRandomWind(const char * arg)1256 fgOptRandomWind( const char *arg )
1257 {
1258     double min_hdg = sg_random() * 360.0;
1259     double max_hdg = min_hdg + (20 - sqrt(sg_random() * 400));
1260     double speed = sg_random() * sg_random() * 40;
1261     double gust = speed + (10 - sqrt(sg_random() * 100));
1262     Environment::Presets::WindSingleton::instance()->preset(min_hdg, max_hdg, speed, gust);
1263     return FG_OPTIONS_OK;
1264 }
1265 
1266 static int
fgOptWind(const char * arg)1267 fgOptWind( const char *arg )
1268 {
1269     double min_hdg = 0.0, max_hdg = 0.0, speed = 0.0, gust = 0.0;
1270     if (!parse_wind( arg, &min_hdg, &max_hdg, &speed, &gust)) {
1271 	SG_LOG( SG_GENERAL, SG_ALERT, "bad wind value " << arg );
1272 	return FG_OPTIONS_ERROR;
1273     }
1274     Environment::Presets::WindSingleton::instance()->preset(min_hdg, max_hdg, speed, gust);
1275     return FG_OPTIONS_OK;
1276 }
1277 
1278 static int
fgOptTurbulence(const char * arg)1279 fgOptTurbulence( const char *arg )
1280 {
1281     Environment::Presets::TurbulenceSingleton::instance()->preset( atof(arg) );
1282     return FG_OPTIONS_OK;
1283 }
1284 
1285 static int
fgOptCeiling(const char * arg)1286 fgOptCeiling( const char *arg )
1287 {
1288     double elevation, thickness;
1289     string spec = arg;
1290     string::size_type pos = spec.find(':');
1291     if (pos == string::npos) {
1292         elevation = atof(spec.c_str());
1293         thickness = 2000;
1294     } else {
1295         elevation = atof(spec.substr(0, pos).c_str());
1296         thickness = atof(spec.substr(pos + 1).c_str());
1297     }
1298     Environment::Presets::CeilingSingleton::instance()->preset( elevation, thickness );
1299     return FG_OPTIONS_OK;
1300 }
1301 
1302 static int
fgOptWp(const char * arg)1303 fgOptWp( const char *arg )
1304 {
1305     string_list *waypoints = globals->get_initial_waypoints();
1306     if (!waypoints) {
1307         waypoints = new string_list;
1308         globals->set_initial_waypoints(waypoints);
1309     }
1310     waypoints->push_back(arg);
1311     return FG_OPTIONS_OK;
1312 }
1313 
1314 static bool
parse_colon(const string & s,double * val1,double * val2)1315 parse_colon (const string &s, double * val1, double * val2)
1316 {
1317     string::size_type pos = s.find(':');
1318     if (pos == string::npos) {
1319         *val2 = atof(s);
1320         return false;
1321     } else {
1322         *val1 = atof(s.substr(0, pos).c_str());
1323         *val2 = atof(s.substr(pos+1).c_str());
1324         return true;
1325     }
1326 }
1327 
1328 
1329 static int
fgOptFailure(const char * arg)1330 fgOptFailure( const char * arg )
1331 {
1332     string a = arg;
1333     if (a == "pitot") {
1334         fgSetBool("/systems/pitot/serviceable", false);
1335     } else if (a == "static") {
1336         fgSetBool("/systems/static/serviceable", false);
1337     } else if (a == "vacuum") {
1338         fgSetBool("/systems/vacuum/serviceable", false);
1339     } else if (a == "electrical") {
1340         fgSetBool("/systems/electrical/serviceable", false);
1341     } else {
1342         SG_LOG(SG_INPUT, SG_ALERT, "Unknown failure mode: " << a);
1343         return FG_OPTIONS_ERROR;
1344     }
1345 
1346     return FG_OPTIONS_OK;
1347 }
1348 
1349 
1350 static int
fgOptNAV1(const char * arg)1351 fgOptNAV1( const char * arg )
1352 {
1353     double radial, freq;
1354     if (parse_colon(arg, &radial, &freq))
1355         fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", radial);
1356     fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", freq);
1357     return FG_OPTIONS_OK;
1358 }
1359 
1360 static int
fgOptNAV2(const char * arg)1361 fgOptNAV2( const char * arg )
1362 {
1363     double radial, freq;
1364     if (parse_colon(arg, &radial, &freq))
1365         fgSetDouble("/instrumentation/nav[1]/radials/selected-deg", radial);
1366     fgSetDouble("/instrumentation/nav[1]/frequencies/selected-mhz", freq);
1367     return FG_OPTIONS_OK;
1368 }
1369 
1370 static int
fgOptADF(const char * arg)1371 fgOptADF( const char * arg )
1372 {
1373     SG_LOG(SG_ALL,SG_ALERT,
1374            "the option --adf is no longer supported! Please use --adf1 "
1375            "instead.");
1376     return FG_OPTIONS_EXIT;
1377 }
1378 
1379 static int
fgOptADF1(const char * arg)1380 fgOptADF1( const char * arg )
1381 {
1382     double rot, freq;
1383     if (parse_colon(arg, &rot, &freq))
1384         fgSetDouble("/instrumentation/adf[0]/rotation-deg", rot);
1385     fgSetDouble("/instrumentation/adf[0]/frequencies/selected-khz", freq);
1386     return FG_OPTIONS_OK;
1387 }
1388 
1389 static int
fgOptADF2(const char * arg)1390 fgOptADF2( const char * arg )
1391 {
1392     double rot, freq;
1393     if (parse_colon(arg, &rot, &freq))
1394         fgSetDouble("/instrumentation/adf[1]/rotation-deg", rot);
1395     fgSetDouble("/instrumentation/adf[1]/frequencies/selected-khz", freq);
1396     return FG_OPTIONS_OK;
1397 }
1398 
1399 static int
fgOptDME(const char * arg)1400 fgOptDME( const char *arg )
1401 {
1402     string opt = arg;
1403     if (opt == "nav1") {
1404         fgSetInt("/instrumentation/dme/switch-position", 1);
1405         fgSetString("/instrumentation/dme/frequencies/source",
1406                     "/instrumentation/nav[0]/frequencies/selected-mhz");
1407     } else if (opt == "nav2") {
1408         fgSetInt("/instrumentation/dme/switch-position", 3);
1409         fgSetString("/instrumentation/dme/frequencies/source",
1410                     "/instrumentation/nav[1]/frequencies/selected-mhz");
1411     } else {
1412         double frequency = atof(arg);
1413         if (frequency==0.0)
1414         {
1415             SG_LOG(SG_INPUT, SG_ALERT, "Invalid DME frequency: '" << arg << "'.");
1416             return FG_OPTIONS_ERROR;
1417         }
1418         fgSetInt("/instrumentation/dme/switch-position", 2);
1419         fgSetString("/instrumentation/dme/frequencies/source",
1420                     "/instrumentation/dme/frequencies/selected-mhz");
1421         fgSetDouble("/instrumentation/dme/frequencies/selected-mhz", frequency);
1422     }
1423     return FG_OPTIONS_OK;
1424 }
1425 
1426 static int
fgOptLivery(const char * arg)1427 fgOptLivery( const char *arg )
1428 {
1429     string opt = arg;
1430     string livery_path = "livery/" + opt;
1431     fgSetString("/sim/model/texture-path", livery_path.c_str() );
1432     return FG_OPTIONS_OK;
1433 }
1434 
1435 static int
fgOptScenario(const char * arg)1436 fgOptScenario( const char *arg )
1437 {
1438     SGPath path(arg);
1439     std::string name(arg);
1440     if (path.exists()) {
1441         if (path.isRelative()) {
1442             // make absolute
1443             path = simgear::Dir::current().path() / arg;
1444         }
1445 
1446         // create description node
1447         auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
1448         if (!n) {
1449             SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
1450             return FG_OPTIONS_ERROR;
1451         }
1452 
1453         // also set the /sim/ai/scenario entry so we load it on startup
1454         name = path.file_base();
1455     }
1456 
1457     // add the 'load it' node
1458     SGPropertyNode_ptr ai_node = fgGetNode( "/sim/ai", true );
1459     ai_node->addChild("scenario")->setStringValue(name);
1460 
1461     return FG_OPTIONS_OK;
1462 }
1463 
1464 static int
fgOptAirport(const char * arg)1465 fgOptAirport( const char *arg )
1466 {
1467     fgSetString("/sim/presets/airport-id", arg );
1468     fgSetBool("/sim/presets/airport-requested", true );
1469     return FG_OPTIONS_OK;
1470 }
1471 
1472 static int
fgOptRunway(const char * arg)1473 fgOptRunway( const char *arg )
1474 {
1475     fgSetString("/sim/presets/runway", arg );
1476     fgSetBool("/sim/presets/runway-requested", true );
1477     return FG_OPTIONS_OK;
1478 }
1479 
1480 static int
fgOptCallSign(const char * arg)1481 fgOptCallSign(const char * arg)
1482 {
1483     int i;
1484     char callsign[11];
1485     strncpy(callsign,arg,10);
1486     callsign[10]=0;
1487     for (i=0;callsign[i];i++)
1488     {
1489         char c = callsign[i];
1490         if (c >= 'A' && c <= 'Z') continue;
1491         if (c >= 'a' && c <= 'z') continue;
1492         if (c >= '0' && c <= '9') continue;
1493         if (c == '-' || c == '_') continue;
1494         // convert any other illegal characters
1495         callsign[i]='-';
1496     }
1497     fgSetString("sim/multiplay/callsign", callsign );
1498     return FG_OPTIONS_OK;
1499 }
1500 
1501 static int
fgOptIgnoreAutosave(const char * arg)1502 fgOptIgnoreAutosave(const char* arg)
1503 {
1504     fgSetBool("/sim/startup/ignore-autosave", true);
1505     // don't overwrite autosave on exit
1506     fgSetBool("/sim/startup/save-on-exit", false);
1507     return FG_OPTIONS_OK;
1508 }
1509 
1510 static int
fgOptEnableFreeze(const char * arg)1511 fgOptEnableFreeze(const char* arg)
1512 {
1513     fgSetBool("/sim/freeze/master", true);
1514     fgSetBool("/sim/freeze/clock", true);
1515     return FG_OPTIONS_OK;
1516 }
1517 
1518 static int
fgOptDisableFreeze(const char * arg)1519 fgOptDisableFreeze(const char* arg)
1520 {
1521     fgSetBool("/sim/freeze/master", false);
1522     fgSetBool("/sim/freeze/clock", false);
1523     return FG_OPTIONS_OK;
1524 }
1525 
1526 // Set a property for the --prop: option. Syntax: --prop:[<type>:]<name>=<value>
1527 // <type> can be "double" etc. but also only the first letter "d".
1528 // Examples:  --prop:alpha=1  --prop:bool:beta=true  --prop:d:gamma=0.123
1529 static int
fgOptSetProperty(const char * raw)1530 fgOptSetProperty(const char* raw)
1531 {
1532   string arg(raw);
1533   string::size_type pos = arg.find('=');
1534   if (pos == arg.npos || pos == 0 || pos + 1 == arg.size())
1535     return FG_OPTIONS_ERROR;
1536 
1537   string name = arg.substr(0, pos);
1538   string value = arg.substr(pos + 1);
1539   string type;
1540   pos = name.find(':');
1541 
1542   if (pos != name.npos && pos != 0 && pos + 1 != name.size()) {
1543     type = name.substr(0, pos);
1544     name = name.substr(pos + 1);
1545   }
1546   SGPropertyNode *n = fgGetNode(name.c_str(), true);
1547 
1548   bool writable = n->getAttribute(SGPropertyNode::WRITE);
1549   if (!writable)
1550     n->setAttribute(SGPropertyNode::WRITE, true);
1551 
1552   bool ret = false;
1553   if (type.empty())
1554     ret = n->setUnspecifiedValue(value.c_str());
1555   else if (type == "s" || type == "string")
1556     ret = n->setStringValue(value.c_str());
1557   else if (type == "d" || type == "double")
1558     ret = n->setDoubleValue(strtod(value.c_str(), 0));
1559   else if (type == "f" || type == "float")
1560     ret = n->setFloatValue(atof(value.c_str()));
1561   else if (type == "l" || type == "long")
1562     ret =  n->setLongValue(strtol(value.c_str(), 0, 0));
1563   else if (type == "i" || type == "int")
1564     ret =  n->setIntValue(atoi(value.c_str()));
1565   else if (type == "b" || type == "bool")
1566     ret =  n->setBoolValue(value == "true" || atoi(value.c_str()) != 0);
1567 
1568   if (!writable)
1569     n->setAttribute(SGPropertyNode::WRITE, false);
1570   return ret ? FG_OPTIONS_OK : FG_OPTIONS_ERROR;
1571 }
1572 
1573 static int
fgOptLoadTape(const char * arg)1574 fgOptLoadTape(const char* arg)
1575 {
1576   // load a flight recorder tape but wait until the fdm is initialized
1577   class DelayedTapeLoader : SGPropertyChangeListener {
1578   public:
1579     DelayedTapeLoader( const char * tape ) :
1580       _tape(SGPath::fromUtf8(tape))
1581     {
1582       SGPropertyNode_ptr n = fgGetNode("/sim/signals/fdm-initialized", true);
1583       n->addChangeListener( this );
1584     }
1585 
1586     virtual ~ DelayedTapeLoader() {}
1587 
1588     virtual void valueChanged(SGPropertyNode * node)
1589     {
1590       node->removeChangeListener( this );
1591 
1592       // tell the replay subsystem to load the tape
1593       FGReplay* replay = globals->get_subsystem<FGReplay>();
1594       SGPropertyNode_ptr arg = new SGPropertyNode();
1595       arg->setStringValue("tape", _tape.utf8Str() );
1596       arg->setBoolValue( "same-aircraft", 0 );
1597       replay->loadTape(arg);
1598 
1599       delete this; // commence suicide
1600     }
1601   private:
1602     SGPath _tape;
1603 
1604   };
1605 
1606   new DelayedTapeLoader(arg);
1607   return FG_OPTIONS_OK;
1608 }
1609 
fgOptDisableGUI(const char *)1610 static int fgOptDisableGUI(const char*)
1611 {
1612     globals->set_headless(true);
1613     return FG_OPTIONS_OK;
1614 }
1615 
1616 /*
1617    option       has_param type        property         b_param s_param  func
1618 
1619 where:
1620  option    : name of the option
1621  has_param : option is --name=value if true or --name if false
1622  type      : OPTION_BOOL    - property is a boolean
1623              OPTION_STRING  - property is a string
1624              OPTION_DOUBLE  - property is a double
1625              OPTION_INT     - property is an integer
1626              OPTION_CHANNEL - name of option is the name of a channel
1627              OPTION_FUNC    - the option trigger a function
1628  b_param   : if type==OPTION_BOOL,
1629              value set to the property (has_param is false for boolean)
1630  s_param   : if type==OPTION_STRING,
1631              value set to the property if has_param is false
1632  func      : function called if type==OPTION_FUNC. if has_param is true,
1633              the value is passed to the function as a string, otherwise,
1634              s_param is passed.
1635 
1636     For OPTION_DOUBLE and OPTION_INT, the parameter value is converted into a
1637     double or an integer and set to the property.
1638 
1639     For OPTION_CHANNEL, add_channel is called with the parameter value as the
1640     argument.
1641 */
1642 
1643 enum OptionType { OPTION_BOOL = 0, OPTION_STRING, OPTION_DOUBLE, OPTION_INT, OPTION_CHANNEL, OPTION_FUNC, OPTION_IGNORE };
1644 const int OPTION_MULTI = 1 << 17;
1645 
1646 struct OptionDesc {
1647     const char *option;
1648     bool has_param;
1649     int type;
1650     const char *property;
1651     bool b_param;
1652     const char *s_param;
1653     int (*func)( const char * );
1654     } fgOptionArray[] = {
1655 
1656     {"language",                     true,  OPTION_IGNORE, "", false, "", 0 },
1657 	{"console",                      false, OPTION_FUNC,   "", false, "", fgOptConsole },
1658     {"launcher",                     false, OPTION_IGNORE,   "", false, "", 0 },
1659     {"enable-sentry",                false, OPTION_BOOL,   "/sim/startup/sentry-crash-reporting-enabled", true, "", nullptr },
1660     {"disable-sentry",               false, OPTION_BOOL,   "/sim/startup/sentry-crash-reporting-enabled", false, "", nullptr },
1661     {"allow-nasal-from-sockets",     false, OPTION_IGNORE,   "", false, "", 0 },
1662     {"disable-rembrandt",            false, OPTION_BOOL,   "/sim/rendering/rembrandt/enabled", false, "", 0 },
1663     {"enable-rembrandt",             false, OPTION_BOOL,   "/sim/rendering/rembrandt/enabled", true, "", 0 },
1664     {"renderer",                     true,  OPTION_STRING, "/sim/rendering/rembrandt/renderer", false, "", 0 },
1665     {"compositor",                   true,  OPTION_STRING, "/sim/rendering/default-compositor", false, "", 0 },
1666     {"disable-splash-screen",        false, OPTION_BOOL,   "/sim/startup/splash-screen", false, "", 0 },
1667     {"enable-splash-screen",         false, OPTION_BOOL,   "/sim/startup/splash-screen", true, "", 0 },
1668     {"disable-mouse-pointer",        false, OPTION_STRING, "/sim/startup/mouse-pointer", false, "disabled", 0 },
1669     {"enable-mouse-pointer",         false, OPTION_STRING, "/sim/startup/mouse-pointer", false, "enabled", 0 },
1670     {"disable-random-objects",       false, OPTION_BOOL,   "/sim/rendering/random-objects", false, "", 0 },
1671     {"enable-random-objects",        false, OPTION_BOOL,   "/sim/rendering/random-objects", true, "", 0 },
1672     {"disable-random-vegetation",    false, OPTION_BOOL,   "/sim/rendering/random-vegetation", false, "", 0 },
1673     {"enable-random-vegetation",     false, OPTION_BOOL,   "/sim/rendering/random-vegetation", true, "", 0 },
1674     {"disable-random-buildings",     false, OPTION_BOOL,   "/sim/rendering/random-buildings", false, "", 0 },
1675     {"enable-random-buildings",      false, OPTION_BOOL,   "/sim/rendering/random-buildings", true, "", 0 },
1676     {"disable-real-weather-fetch",   false, OPTION_BOOL,   "/environment/realwx/enabled", false, "", 0 },
1677     {"enable-real-weather-fetch",    false, OPTION_BOOL,   "/environment/realwx/enabled", true,  "", 0 },
1678     {"metar",                        true,  OPTION_FUNC,   "", false, "", fgOptMetar },
1679     {"disable-ai-models",            false, OPTION_BOOL,   "/sim/ai/enabled", false, "", 0 },
1680     {"enable-ai-models",             false, OPTION_BOOL,   "/sim/ai/enabled", true, "", 0 },
1681     {"disable-ai-traffic",           false, OPTION_BOOL,   "/sim/traffic-manager/enabled", false, "", 0 },
1682     {"enable-ai-traffic",            false, OPTION_BOOL,   "/sim/traffic-manager/enabled", true,  "", 0 },
1683     {"disable-freeze",               false, OPTION_FUNC,   "", false, "", fgOptDisableFreeze },
1684     {"enable-freeze",                false, OPTION_FUNC,   "", true, "", fgOptEnableFreeze },
1685     {"disable-fuel-freeze",          false, OPTION_BOOL,   "/sim/freeze/fuel", false, "", 0 },
1686     {"enable-fuel-freeze",           false, OPTION_BOOL,   "/sim/freeze/fuel", true, "", 0 },
1687     {"disable-clock-freeze",         false, OPTION_BOOL,   "/sim/freeze/clock", false, "", 0 },
1688     {"enable-clock-freeze",          false, OPTION_BOOL,   "/sim/freeze/clock", true, "", 0 },
1689     {"disable-hud-3d",               false, OPTION_BOOL,   "/sim/hud/enable3d[1]", false, "", 0 },
1690     {"enable-hud-3d",                false, OPTION_BOOL,   "/sim/hud/enable3d[1]", true, "", 0 },
1691     {"disable-anti-alias-hud",       false, OPTION_BOOL,   "/sim/hud/color/antialiased", false, "", 0 },
1692     {"enable-anti-alias-hud",        false, OPTION_BOOL,   "/sim/hud/color/antialiased", true, "", 0 },
1693     {"disable-auto-coordination",    false, OPTION_BOOL,   "/controls/flight/auto-coordination", false, "", 0 },
1694     {"enable-auto-coordination",     false, OPTION_BOOL,   "/controls/flight/auto-coordination", true, "", 0 },
1695     {"browser-app",                  true,  OPTION_STRING, "/sim/startup/browser-app", false, "", 0 },
1696     {"disable-hud",                  false, OPTION_BOOL,   "/sim/hud/visibility[1]", false, "", 0 },
1697     {"enable-hud",                   false, OPTION_BOOL,   "/sim/hud/visibility[1]", true, "", 0 },
1698     {"disable-panel",                false, OPTION_BOOL,   "/sim/panel/visibility", false, "", 0 },
1699     {"enable-panel",                 false, OPTION_BOOL,   "/sim/panel/visibility", true, "", 0 },
1700     {"disable-sound",                false, OPTION_BOOL,   "/sim/sound/working", false, "", 0 },
1701     {"enable-sound",                 false, OPTION_BOOL,   "/sim/sound/working", true, "", 0 },
1702     {"sound-device",                 true,  OPTION_STRING, "/sim/sound/device-name", false, "", 0 },
1703     {"airport",                      true,  OPTION_FUNC,   "", false, "", fgOptAirport },
1704     {"runway",                       true,  OPTION_FUNC,   "", false, "", fgOptRunway },
1705     {"vor",                          true,  OPTION_FUNC,   "", false, "", fgOptVOR },
1706     {"vor-frequency",                true,  OPTION_DOUBLE, "/sim/presets/vor-freq", false, "", fgOptVOR },
1707     {"ndb",                          true,  OPTION_FUNC,   "", false, "", fgOptNDB },
1708     {"ndb-frequency",                true,  OPTION_DOUBLE, "/sim/presets/ndb-freq", false, "", fgOptVOR },
1709     {"carrier",                      true,  OPTION_FUNC,   "", false, "", fgOptCarrier },
1710     {"carrier-position",             true,  OPTION_FUNC,   "", false, "", fgOptCarrierPos },
1711     {"fix",                          true,  OPTION_FUNC,   "", false, "", fgOptFIX },
1712     {"offset-distance",              true,  OPTION_DOUBLE, "/sim/presets/offset-distance-nm", false, "", 0 },
1713     {"offset-azimuth",               true,  OPTION_DOUBLE, "/sim/presets/offset-azimuth-deg", false, "", 0 },
1714     {"lon",                          true,  OPTION_FUNC,   "", false, "", fgOptLon },
1715     {"lat",                          true,  OPTION_FUNC,   "", false, "", fgOptLat },
1716     {"altitude",                     true,  OPTION_FUNC,   "", false, "", fgOptAltitude },
1717     {"uBody",                        true,  OPTION_FUNC,   "", false, "", fgOptUBody },
1718     {"vBody",                        true,  OPTION_FUNC,   "", false, "", fgOptVBody },
1719     {"wBody",                        true,  OPTION_FUNC,   "", false, "", fgOptWBody },
1720     {"vNorth",                       true,  OPTION_FUNC,   "", false, "", fgOptVNorth },
1721     {"vEast",                        true,  OPTION_FUNC,   "", false, "", fgOptVEast },
1722     {"vDown",                        true,  OPTION_FUNC,   "", false, "", fgOptVDown },
1723     {"vc",                           true,  OPTION_FUNC,   "", false, "", fgOptVc },
1724     {"mach",                         true,  OPTION_FUNC,   "", false, "", fgOptMach },
1725     {"heading",                      true,  OPTION_DOUBLE, "/sim/presets/heading-deg", false, "", 0 },
1726     {"roll",                         true,  OPTION_DOUBLE, "/sim/presets/roll-deg", false, "", 0 },
1727     {"pitch",                        true,  OPTION_DOUBLE, "/sim/presets/pitch-deg", false, "", 0 },
1728     {"glideslope",                   true,  OPTION_DOUBLE, "/sim/presets/glideslope-deg", false, "", 0 },
1729     {"roc",                          true,  OPTION_FUNC,   "", false, "", fgOptRoc },
1730     {"fg-root",                      true,  OPTION_IGNORE,   "", false, "", 0 },
1731     {"fg-scenery",                   true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptFgScenery },
1732     {"terrain-engine",               true,  OPTION_STRING, "/sim/scenery/engine", false, "tilecache", 0 },
1733     {"lod-levels",                   true,  OPTION_STRING, "/sim/scenery/lod-levels", false, "1 3 5 7", 0 },
1734     {"lod-res",                      true,  OPTION_STRING, "/sim/scenery/lod-res", false, "1", 0 },
1735     {"lod-texturing",                true,  OPTION_STRING, "/sim/scenery/lod-texturing", false, "bluemarble", 0 },
1736     {"lod-range-mult",               true,  OPTION_STRING, "/sim/scenery/lod-range-mult", false, "2", 0 },
1737     {"fg-aircraft",                  true,  OPTION_IGNORE | OPTION_MULTI,   "", false, "", 0 },
1738     {"fdm",                          true,  OPTION_STRING, "/sim/flight-model", false, "", 0 },
1739     {"aero",                         true,  OPTION_STRING, "/sim/aero", false, "", 0 },
1740     {"aircraft-dir",                 true,  OPTION_IGNORE,   "", false, "", 0 },
1741     {"state",                        true,  OPTION_IGNORE,   "", false, "", 0 },
1742     {"model-hz",                     true,  OPTION_INT,    "/sim/model-hz", false, "", 0 },
1743     {"max-fps",                      true,  OPTION_DOUBLE, "/sim/frame-rate-throttle-hz", false, "", 0 },
1744     {"speed",                        true,  OPTION_DOUBLE, "/sim/speed-up", false, "", 0 },
1745     {"trim",                         false, OPTION_BOOL,   "/sim/presets/trim", true, "", 0 },
1746     {"notrim",                       false, OPTION_BOOL,   "/sim/presets/trim", false, "", 0 },
1747     {"on-ground",                    false, OPTION_BOOL,   "/sim/presets/onground", true, "", 0 },
1748     {"in-air",                       false, OPTION_BOOL,   "/sim/presets/onground", false, "", 0 },
1749     {"disable-hold-short",           false, OPTION_BOOL,   "/sim/presets/mp-hold-short-override", true, "", 0 },
1750     {"fog-disable",                  false, OPTION_STRING, "/sim/rendering/fog", false, "disabled", 0 },
1751     {"fog-fastest",                  false, OPTION_STRING, "/sim/rendering/fog", false, "fastest", 0 },
1752     {"fog-nicest",                   false, OPTION_STRING, "/sim/rendering/fog", false, "nicest", 0 },
1753     {"disable-horizon-effect",       false, OPTION_BOOL,   "/sim/rendering/horizon-effect", false, "", 0 },
1754     {"enable-horizon-effect",        false, OPTION_BOOL,   "/sim/rendering/horizon-effect", true, "", 0 },
1755     {"disable-enhanced-lighting",    false, OPTION_FUNC,   "", false, "", fgOptEnhancedLighting },
1756     {"enable-enhanced-lighting",     false, OPTION_FUNC,   "", false, "", fgOptEnhancedLighting },
1757     {"disable-distance-attenuation", false, OPTION_BOOL,   "/sim/rendering/distance-attenuation", false, "", 0 },
1758     {"enable-distance-attenuation",  false, OPTION_BOOL,   "/sim/rendering/distance-attenuation", true, "", 0 },
1759     {"disable-specular-highlight",   false, OPTION_BOOL,   "/sim/rendering/specular-highlight", false, "", 0 },
1760     {"enable-specular-highlight",    false, OPTION_BOOL,   "/sim/rendering/specular-highlight", true, "", 0 },
1761     {"disable-clouds",               false, OPTION_BOOL,   "/environment/clouds/status", false, "", 0 },
1762     {"enable-clouds",                false, OPTION_BOOL,   "/environment/clouds/status", true, "", 0 },
1763     {"disable-clouds3d",             false, OPTION_BOOL,   "/sim/rendering/clouds3d-enable", false, "", 0 },
1764     {"enable-clouds3d",              false, OPTION_BOOL,   "/sim/rendering/clouds3d-enable", true, "", 0 },
1765     {"fov",                          true,  OPTION_FUNC,   "", false, "", fgOptFov },
1766     {"aspect-ratio-multiplier",      true,  OPTION_DOUBLE, "/sim/current-view/aspect-ratio-multiplier", false, "", 0 },
1767     {"disable-fullscreen",           false, OPTION_BOOL,   "/sim/startup/fullscreen", false, "", 0 },
1768     {"enable-fullscreen",            false, OPTION_BOOL,   "/sim/startup/fullscreen", true, "", 0 },
1769     {"disable-save-on-exit",         false, OPTION_BOOL,   "/sim/startup/save-on-exit", false, "", 0 },
1770     {"enable-save-on-exit",          false, OPTION_BOOL,   "/sim/startup/save-on-exit", true, "", 0 },
1771     {"read-only",                    false, OPTION_BOOL,   "/sim/fghome-readonly", true, "", 0 },
1772     {"ignore-autosave",              false, OPTION_FUNC,   "", false, "", fgOptIgnoreAutosave },
1773     {"restore-defaults",             false, OPTION_BOOL,   "/sim/startup/restore-defaults", true, "", 0 },
1774     {"shading-flat",                 false, OPTION_BOOL,   "/sim/rendering/shading", false, "", 0 },
1775     {"shading-smooth",               false, OPTION_BOOL,   "/sim/rendering/shading", true, "", 0 },
1776     {"texture-filtering",            false, OPTION_INT,    "/sim/rendering/filtering", 1, "", 0 },
1777     {"disable-wireframe",            false, OPTION_BOOL,   "/sim/rendering/wireframe", false, "", 0 },
1778     {"enable-wireframe",             false, OPTION_BOOL,   "/sim/rendering/wireframe", true, "", 0 },
1779     {"materials-file",               true,  OPTION_STRING, "/sim/rendering/materials-file", false, "", 0 },
1780     {"disable-terrasync",            false, OPTION_BOOL,   "/sim/terrasync/enabled", false, "", 0 },
1781     {"enable-terrasync",             false, OPTION_BOOL,   "/sim/terrasync/enabled", true, "", 0 },
1782     {"terrasync-dir",                true,  OPTION_IGNORE,   "", false, "", 0 },
1783     {"download-dir",                 true,  OPTION_IGNORE,   "", false, "", 0 },
1784     {"texture-cache-dir",            true,  OPTION_IGNORE,   "", false, "", 0 },
1785     {"enable-texture-cache",         false, OPTION_BOOL,   "/sim/rendering/texture-cache/cache-enabled", true, "", 0 },
1786     {"disable-texture-cache",        false, OPTION_BOOL,   "/sim/rendering/texture-cache/cache-enabled", false, "", 0 },
1787     {"allow-nasal-read",             true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptAllowNasalRead },
1788     {"geometry",                     true,  OPTION_FUNC,   "", false, "", fgOptGeometry },
1789     {"bpp",                          true,  OPTION_FUNC,   "", false, "", fgOptBpp },
1790     {"units-feet",                   false, OPTION_STRING, "/sim/startup/units", false, "feet", 0 },
1791     {"units-meters",                 false, OPTION_STRING, "/sim/startup/units", false, "meters", 0 },
1792     {"timeofday",                    true,  OPTION_STRING, "/sim/startup/time-offset-type", false, "noon", 0 },
1793     {"season",                       true,  OPTION_STRING, "/sim/startup/season", false, "summer", 0 },
1794     {"time-offset",                  true,  OPTION_FUNC,   "", false, "", fgOptTimeOffset },
1795     {"time-match-real",              false, OPTION_STRING, "/sim/startup/time-offset-type", false, "system-offset", 0 },
1796     {"time-match-local",             false, OPTION_STRING, "/sim/startup/time-offset-type", false, "latitude-offset", 0 },
1797     {"start-date-sys",               true,  OPTION_FUNC,   "", false, "", fgOptStartDateSys },
1798     {"start-date-lat",               true,  OPTION_FUNC,   "", false, "", fgOptStartDateLat },
1799     {"start-date-gmt",               true,  OPTION_FUNC,   "", false, "", fgOptStartDateGmt },
1800     {"hud-tris",                     false, OPTION_STRING, "/sim/hud/frame-stat-type", false, "tris", 0 },
1801     {"hud-culled",                   false, OPTION_STRING, "/sim/hud/frame-stat-type", false, "culled", 0 },
1802     {"atcsim",                       true,  OPTION_CHANNEL, "", false, "dummy", 0 },
1803     {"atlas",                        true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1804     {"httpd",                        true,  OPTION_FUNC   , "", false, "", fgOptHttpd },
1805     {"jpg-httpd",                    true,  OPTION_FUNC,    "", false, "", fgOptJpgHttpd },
1806     {"native",                       true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1807     {"native-ctrls",                 true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1808     {"native-fdm",                   true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1809     {"native-gui",                   true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1810     {"opengc",                       true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1811     {"AV400",                        true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1812     {"AV400Sim",                     true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1813     {"AV400WSimA",                   true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1814     {"AV400WSimB",                   true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1815     {"flarm",                        true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1816     {"garmin",                       true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1817     {"igc",                          true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1818     {"nmea",                         true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1819     {"generic",                      true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1820     {"props",                        true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1821     {"telnet",                       true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1822     {"pve",                          true,  OPTION_CHANNEL, "", false, "", 0 },
1823     {"ray",                          true,  OPTION_CHANNEL, "", false, "", 0 },
1824     {"rul",                          true,  OPTION_CHANNEL, "", false, "", 0 },
1825     {"joyclient",                    true,  OPTION_CHANNEL, "", false, "", 0 },
1826     {"jsclient",                     true,  OPTION_CHANNEL, "", false, "", 0 },
1827     {"proxy",                        true,  OPTION_FUNC,    "", false, "", fgSetupProxy },
1828     {"callsign",                     true,  OPTION_FUNC,    "", false, "", fgOptCallSign},
1829     {"multiplay",                    true,  OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1830 #if FG_HAVE_HLA
1831     {"hla",                          true,  OPTION_CHANNEL, "", false, "", 0 },
1832     {"hla-local",                    true,  OPTION_CHANNEL, "", false, "", 0 },
1833 #endif
1834     {"trace-read",                   true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptTraceRead },
1835     {"trace-write",                  true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptTraceWrite },
1836     {"log-level",                    true,  OPTION_FUNC,   "", false, "", fgOptLogLevel },
1837     {"log-class",                    true,  OPTION_FUNC,   "", false, "", fgOptLogClasses },
1838     {"log-dir",                      true,  OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptLogDir },
1839     {"view-offset",                  true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptViewOffset },
1840     {"visibility",                   true,  OPTION_FUNC,   "", false, "", fgOptVisibilityMeters },
1841     {"visibility-miles",             true,  OPTION_FUNC,   "", false, "", fgOptVisibilityMiles },
1842     {"random-wind",                  false, OPTION_FUNC,   "", false, "", fgOptRandomWind },
1843     {"wind",                         true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptWind },
1844     {"turbulence",                   true,  OPTION_FUNC,   "", false, "", fgOptTurbulence },
1845     {"ceiling",                      true,  OPTION_FUNC,   "", false, "", fgOptCeiling },
1846     {"wp",                           true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptWp },
1847     {"flight-plan",                  true,  OPTION_STRING,   "/autopilot/route-manager/file-path", false, "", NULL },
1848     {"config",                       true,  OPTION_IGNORE | OPTION_MULTI,   "", false, "", 0 },
1849     {"addon",                        true,  OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptAddon },
1850     {"data",                         true,  OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptAdditionalDataDir },
1851     {"aircraft",                     true,  OPTION_STRING, "/sim/aircraft", false, "", 0 },
1852     {"vehicle",                      true,  OPTION_STRING, "/sim/aircraft", false, "", 0 },
1853     {"failure",                      true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptFailure },
1854 #ifdef ENABLE_IAX
1855     {"enable-fgcom",                 false, OPTION_BOOL,   "/sim/fgcom/enabled", true, "", 0 },
1856     {"disable-fgcom",                false, OPTION_BOOL,   "/sim/fgcom/enabled", false, "", 0 },
1857 #endif
1858     {"com1",                         true,  OPTION_DOUBLE, "/instrumentation/comm[0]/frequencies/selected-mhz", false, "", 0 },
1859     {"com2",                         true,  OPTION_DOUBLE, "/instrumentation/comm[1]/frequencies/selected-mhz", false, "", 0 },
1860     {"nav1",                         true,  OPTION_FUNC,   "", false, "", fgOptNAV1 },
1861     {"nav2",                         true,  OPTION_FUNC,   "", false, "", fgOptNAV2 },
1862     {"adf", /*legacy*/               true,  OPTION_FUNC,   "", false, "", fgOptADF },
1863     {"adf1",                         true,  OPTION_FUNC,   "", false, "", fgOptADF1 },
1864     {"adf2",                         true,  OPTION_FUNC,   "", false, "", fgOptADF2 },
1865     {"dme",                          true,  OPTION_FUNC,   "", false, "", fgOptDME },
1866     {"min-status",                   true,  OPTION_STRING,  "/sim/aircraft-min-status", false, "all", 0 },
1867     {"livery",                       true,  OPTION_FUNC,   "", false, "", fgOptLivery },
1868     {"ai-scenario",                  true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptScenario },
1869     {"parking-id",                   true,  OPTION_FUNC,   "", false, "", fgOptParkpos },
1870     {"parkpos",                      true,  OPTION_FUNC,   "", false, "", fgOptParkpos },
1871     {"version",                      false, OPTION_IGNORE, "", false, "", 0 },
1872     {"json-report",                  false, OPTION_IGNORE, "", false, "", 0 },
1873     {"enable-fpe",                   false, OPTION_IGNORE, "", false, "", 0},
1874     {"fgviewer",                     false, OPTION_IGNORE, "", false, "", 0},
1875     {"no-default-config",            false, OPTION_IGNORE, "", false, "", 0},
1876     {"prop",                         true,  OPTION_FUNC | OPTION_MULTI,   "", false, "", fgOptSetProperty},
1877     {"load-tape",                    true,  OPTION_FUNC,   "", false, "", fgOptLoadTape },
1878     {"developer",                    true,  OPTION_IGNORE | OPTION_BOOL, "", false, "", nullptr },
1879     {"jsbsim-output-directive-file", true,  OPTION_STRING, "/sim/jsbsim/output-directive-file", false, "", nullptr },
1880     {"disable-gui",                  false, OPTION_FUNC, "", false, "", fgOptDisableGUI },
1881     {"restart-launcher",             false, OPTION_BOOL, "/sim/restart-launcher-on-exit", true, "", nullptr},
1882     {nullptr,                        false, 0,             nullptr, false, nullptr, nullptr}
1883 };
1884 
1885 namespace flightgear
1886 {
1887 
1888 /**
1889  * internal storage of a value->option binding
1890  */
1891 class OptionValue
1892 {
1893 public:
OptionValue(OptionDesc * d,const string & v)1894   OptionValue(OptionDesc* d, const string& v) :
1895     desc(d), value(v)
1896   {;}
1897 
1898   OptionDesc* desc;
1899   string value;
1900 };
1901 
1902 typedef std::vector<OptionValue> OptionValueVec;
1903 typedef std::map<string, OptionDesc*> OptionDescDict;
1904 
1905 class Options::OptionsPrivate
1906 {
1907 public:
1908 
findValue(const string & key) const1909   OptionValueVec::const_iterator findValue(const string& key) const
1910   {
1911     OptionValueVec::const_iterator it = values.begin();
1912     for (; it != values.end(); ++it) {
1913       if (!it->desc) {
1914         continue; // ignore markers
1915       }
1916 
1917       if (it->desc->option == key) {
1918         return it;
1919       }
1920     } // of set values iteration
1921 
1922     return it; // not found
1923   }
1924 
findValue(const string & key)1925     OptionValueVec::iterator findValue(const string& key)
1926     {
1927         OptionValueVec::iterator it = values.begin();
1928         for (; it != values.end(); ++it) {
1929             if (!it->desc) {
1930                 continue; // ignore markers
1931             }
1932 
1933             if (it->desc->option == key) {
1934                 return it;
1935             }
1936         } // of set values iteration
1937 
1938         return it; // not found
1939     }
1940 
findOption(const string & key) const1941   OptionDesc* findOption(const string& key) const
1942   {
1943     OptionDescDict::const_iterator it = options.find(key);
1944     if (it == options.end()) {
1945       return NULL;
1946     }
1947 
1948     return it->second;
1949   }
1950 
processOption(OptionDesc * desc,const string & arg_value)1951   int processOption(OptionDesc* desc, const string& arg_value)
1952   {
1953     if (!desc) {
1954       return FG_OPTIONS_OK; // tolerate marker options
1955     }
1956 
1957     switch ( desc->type & 0xffff ) {
1958       case OPTION_BOOL:
1959         fgSetBool( desc->property, desc->b_param );
1960         break;
1961       case OPTION_STRING:
1962         if ( desc->has_param && !arg_value.empty() ) {
1963           fgSetString( desc->property, arg_value.c_str() );
1964         } else if ( !desc->has_param && arg_value.empty() ) {
1965           fgSetString( desc->property, desc->s_param );
1966         } else if ( desc->has_param ) {
1967           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1968           return FG_OPTIONS_ERROR;
1969         } else {
1970           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
1971           return FG_OPTIONS_ERROR;
1972         }
1973         break;
1974       case OPTION_DOUBLE:
1975         if ( !arg_value.empty() ) {
1976           fgSetDouble( desc->property, atof( arg_value ) );
1977         } else {
1978           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1979           return FG_OPTIONS_ERROR;
1980         }
1981         break;
1982       case OPTION_INT:
1983         if ( !arg_value.empty() ) {
1984           fgSetInt( desc->property, atoi( arg_value ) );
1985         } else {
1986           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1987           return FG_OPTIONS_ERROR;
1988         }
1989         break;
1990       case OPTION_CHANNEL:
1991         // XXX return value of add_channel should be checked?
1992         if ( desc->has_param && !arg_value.empty() ) {
1993           add_channel( desc->option, arg_value );
1994         } else if ( !desc->has_param && arg_value.empty() ) {
1995           add_channel( desc->option, desc->s_param );
1996         } else if ( desc->has_param ) {
1997           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1998           return FG_OPTIONS_ERROR;
1999         } else {
2000           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
2001           return FG_OPTIONS_ERROR;
2002         }
2003         break;
2004       case OPTION_FUNC:
2005         if ( desc->has_param && !arg_value.empty() ) {
2006           return desc->func( arg_value.c_str() );
2007         } else if ( !desc->has_param && arg_value.empty() ) {
2008           return desc->func( desc->s_param );
2009         } else if ( desc->has_param ) {
2010           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
2011           return FG_OPTIONS_ERROR;
2012         } else {
2013           SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
2014           return FG_OPTIONS_ERROR;
2015         }
2016         break;
2017 
2018       case OPTION_IGNORE:
2019         break;
2020     }
2021 
2022     return FG_OPTIONS_OK;
2023   }
2024 
2025   /**
2026    * insert a marker value into the values vector. This is necessary
2027    * when processing options, to ensure the correct ordering, where we scan
2028    * for marker values in reverse, and then forwards within each group.
2029    */
insertGroupMarker()2030   void insertGroupMarker()
2031   {
2032     values.push_back(OptionValue(NULL, "-"));
2033   }
2034 
2035   /**
2036    * given a current iterator into the values, find the preceding group marker,
2037    * or return the beginning of the value vector.
2038    */
rfindGroup(OptionValueVec::const_iterator pos) const2039   OptionValueVec::const_iterator rfindGroup(OptionValueVec::const_iterator pos) const
2040   {
2041     while (--pos != values.begin()) {
2042       if (pos->desc == NULL) {
2043         return pos; // found a marker, we're done
2044       }
2045     }
2046 
2047     return pos;
2048   }
2049 
2050   // Return a pointer to a new JSON array node
2051   // (["/foo/bar", "/other/path", ...]) created from the given PathList.
createJSONArrayFromPathList(const PathList & pl) const2052   cJSON *createJSONArrayFromPathList(const PathList& pl) const
2053   {
2054     cJSON *resultNode = cJSON_CreateArray();
2055     cJSON *prevNode = nullptr;
2056     bool isFirst = true;
2057 
2058     for (const SGPath& path : pl) {
2059       cJSON *pathNode = cJSON_CreateString(path.utf8Str().c_str());
2060 
2061       if (isFirst) {
2062         isFirst = false;
2063         resultNode->child = pathNode;
2064       } else {
2065         prevNode->next = pathNode;
2066         pathNode->prev = prevNode;
2067       }
2068 
2069       prevNode = pathNode;
2070     }
2071 
2072     return resultNode;
2073   }
2074 
2075   bool showHelp,
2076     verbose,
2077     showAircraft,
2078     shouldLoadDefaultConfig;
2079 
2080   OptionDescDict options;
2081   OptionValueVec values;
2082   simgear::PathList configFiles;
2083   simgear::PathList propertyFiles;
2084 };
2085 
sharedInstance()2086 Options* Options::sharedInstance()
2087 {
2088   if (shared_instance == NULL) {
2089     shared_instance = new Options;
2090   }
2091 
2092   return shared_instance;
2093 }
2094 
reset()2095 void Options::reset()
2096 {
2097     if (shared_instance != nullptr) {
2098         delete shared_instance;
2099         shared_instance = nullptr;
2100     }
2101 }
2102 
Options()2103 Options::Options() :
2104   p(new OptionsPrivate())
2105 {
2106   p->showHelp = false;
2107   p->verbose = false;
2108   p->showAircraft = false;
2109   p->shouldLoadDefaultConfig = true;
2110 
2111 // build option map
2112   OptionDesc *desc = &fgOptionArray[ 0 ];
2113   while ( desc->option != 0 ) {
2114     p->options[ desc->option ] = desc;
2115     ++desc;
2116   }
2117 }
2118 
~Options()2119 Options::~Options()
2120 {
2121 }
2122 
init(int argc,char ** argv,const SGPath & appDataPath)2123 OptionResult Options::init(int argc, char** argv, const SGPath& appDataPath)
2124 {
2125 // first, process the command line
2126   bool inOptions = true;
2127   for (int i=1; i<argc; ++i) {
2128       // important : on first run after the Gatekeeper quarantine flag is
2129       // cleared, launchd passes us a null argument here. Avoid dying in
2130       // that case.
2131       if (!argv[i]) {
2132           continue;
2133       }
2134 
2135     if (inOptions && (argv[i][0] == '-')) {
2136       if (strcmp(argv[i], "--") == 0) { // end of options delimiter
2137         inOptions = false;
2138         continue;
2139       }
2140 
2141       int result = parseOption(argv[i], /* fromConfigFile */ false);
2142       processArgResult(result);
2143     } else {
2144     // XML properties file
2145         SGPath f = SGPath::fromUtf8(argv[i]);
2146       if (!f.exists()) {
2147         SG_LOG(SG_GENERAL, SG_ALERT, "config file not found:" << f);
2148       } else {
2149         p->propertyFiles.push_back(f);
2150       }
2151     }
2152   } // of arguments iteration
2153   p->insertGroupMarker(); // command line is one group
2154 
2155   // establish log-level before anything else - otherwise it is not possible
2156   // to show extra (debug/info/warning) messages for the start-up phase.
2157   // Leave the simgear logstream default value of SG_ALERT if the argument is
2158   // not supplied.
2159   if (isOptionSet("log-level"))
2160       fgOptLogLevel(valueForOption("log-level").c_str());
2161 
2162   simgear::PathList::const_iterator i;
2163   for (i = p->configFiles.begin(); i != p->configFiles.end(); ++i) {
2164       readConfig(*i);
2165   }
2166 
2167   if (!p->shouldLoadDefaultConfig) {
2168       return setupRoot(argc, argv);
2169   }
2170 
2171 // then config files
2172   SGPath config;
2173 
2174   if( !hostname.empty() ) {
2175     // Check for ~/.fgfsrc.hostname
2176     config = SGPath::home();
2177     config.append(".fgfsrc");
2178     config.concat( "." );
2179     config.concat( hostname );
2180     readConfig(config);
2181   }
2182 
2183 // Check for ~/.fgfsrc
2184     config = SGPath::home();
2185     config.append(".fgfsrc");
2186     readConfig(config);
2187 
2188 // check for a config file in app data
2189   SGPath appDataConfig(appDataPath);
2190   appDataConfig.append("fgfsrc");
2191   if (appDataConfig.exists()) {
2192     readConfig(appDataConfig);
2193   }
2194 
2195 // setup FG_ROOT
2196   auto res = setupRoot(argc, argv);
2197   if (res != FG_OPTIONS_OK) {
2198       return res;
2199   }
2200 
2201 // system.fgfsrc is disabled, as we no longer allow anything in fgdata to set
2202 // fg-root/fg-home/fg-aircraft and hence control what files Nasal can access
2203   std::string nameForError = config.utf8Str();
2204   if( ! hostname.empty() ) {
2205     config = globals->get_fg_root();
2206     config.append( "system.fgfsrc" );
2207     config.concat( "." );
2208     config.concat( hostname );
2209     if (config.exists()) {
2210       flightgear::fatalMessageBoxThenExit(
2211         "Unsupported configuration",
2212         "You have a '" + config.utf8Str() + "' file, which is no longer "
2213         "processed for security reasons.",
2214         "If you created this file intentionally, please move it to '" +
2215         nameForError + "'.");
2216     }
2217   }
2218 
2219   config = globals->get_fg_root();
2220   config.append( "system.fgfsrc" );
2221   if (config.exists()) {
2222     flightgear::fatalMessageBoxThenExit(
2223       "Unsupported configuration",
2224       "You have a '" + config.utf8Str() + "' file, which is no longer "
2225       "processed for security reasons.",
2226       "If you created this file intentionally, please move it to '" +
2227       nameForError + "'.");
2228   }
2229 
2230   return FG_OPTIONS_OK;
2231 }
2232 
initPaths()2233 void Options::initPaths()
2234 {
2235     for (const string& pathOpt : valuesForOption("fg-aircraft")) {
2236         PathList paths = SGPath::pathsFromUtf8(pathOpt);
2237         globals->append_aircraft_paths(paths);
2238     }
2239 
2240     const char* envp = ::getenv("FG_AIRCRAFT");
2241     if (envp) {
2242         globals->append_aircraft_paths(SGPath::pathsFromEnv("FG_AIRCRAFT"));
2243     }
2244 
2245 }
2246 
initAircraft()2247 OptionResult Options::initAircraft()
2248 {
2249   string aircraft;
2250   if (isOptionSet("aircraft")) {
2251     aircraft = valueForOption("aircraft");
2252   } else if (isOptionSet("vehicle")) {
2253     aircraft = valueForOption("vehicle");
2254   }
2255 
2256   if (!aircraft.empty()) {
2257       fgSetString("/sim/aircraft-id", aircraft);
2258       const auto lastDotPos = aircraft.rfind('.');
2259       if (lastDotPos != string::npos) {
2260           // ensure /sim/aircraft is only the local ID, not the fully-qualified ID
2261           // otherwise some existing logic gets confused.
2262           fgSetString("/sim/aircraft", aircraft.substr(lastDotPos + 1));
2263       } else {
2264           fgSetString("/sim/aircraft", aircraft);
2265       }
2266 
2267     SG_LOG(SG_INPUT, SG_INFO, "aircraft = " << aircraft );
2268   } else {
2269     SG_LOG(SG_INPUT, SG_INFO, "No user specified aircraft, using default" );
2270     // ensure aircraft-id is valid
2271     fgSetString("/sim/aircraft-id", fgGetString("/sim/aircraft"));
2272   }
2273 
2274   if (p->showAircraft) {
2275 	PathList path_list;
2276 
2277     fgOptLogLevel( "alert" );
2278 
2279     // First place to check is the 'Aircraft' sub-directory in $FG_ROOT
2280 
2281       SGPath rootAircraft = globals->get_fg_root();
2282       rootAircraft.append("Aircraft");
2283 	path_list.push_back(rootAircraft);
2284 
2285     // Additionally, aircraft may also be found in user-defined places
2286 	// (via $FG_AIRCRAFT or with the '--fg-aircraft' option)
2287       PathList aircraft_paths = globals->get_aircraft_paths();
2288 
2289       path_list.insert(path_list.end(), aircraft_paths.begin(),
2290                        aircraft_paths.end());
2291 
2292     fgShowAircraft(path_list);
2293     // this is to indicate that we did show it
2294     return FG_OPTIONS_SHOW_AIRCRAFT;
2295   }
2296 
2297   if (isOptionSet("aircraft-dir")) {
2298     SGPath aircraftDirPath = SGPath::fromUtf8(valueForOption("aircraft-dir"));
2299     SGPath realAircraftPath = aircraftDirPath.realpath();
2300     globals->append_read_allowed_paths(realAircraftPath);
2301 
2302     // Set this now, so it's available in FindAndCacheAircraft. Use realpath()
2303     // as in FGGlobals::append_aircraft_path(), otherwise fgValidatePath()
2304     // will deny access to resources under this path if one of its components
2305     // is a symlink (which is not a problem, since it was given as is by the
2306     // user---this is very different from a symlink *under* the aircraft dir
2307     // or a scenery dir).
2308     fgSetString("/sim/aircraft-dir", realAircraftPath.utf8Str());
2309   }
2310 
2311     if (isOptionSet("state")) {
2312         std::string stateName = valueForOption("state");
2313         // can't validate this until the -set.xml is parsed
2314         fgSetString("/sim/aircraft-state", stateName);
2315     }
2316 
2317     return FG_OPTIONS_OK;
2318 }
2319 
processArgResult(int result)2320 void Options::processArgResult(int result)
2321 {
2322   if ((result == FG_OPTIONS_HELP) || (result == FG_OPTIONS_ERROR))
2323     p->showHelp = true;
2324   else if (result == FG_OPTIONS_VERBOSE_HELP)
2325     p->verbose = true;
2326   else if (result == FG_OPTIONS_SHOW_AIRCRAFT) {
2327     p->showAircraft = true;
2328   } else if (result == FG_OPTIONS_NO_DEFAULT_CONFIG) {
2329     p->shouldLoadDefaultConfig = false;
2330   } else if (result == FG_OPTIONS_SHOW_SOUND_DEVICES) {
2331     SGSoundMgr smgr;
2332 
2333     smgr.init();
2334     string vendor = smgr.get_vendor();
2335     string renderer = smgr.get_renderer();
2336     cout << renderer << " provided by " << vendor << endl;
2337     cout << endl << "No. Device" << endl;
2338 
2339     vector <std::string>devices = smgr.get_available_devices();
2340     for (vector <std::string>::size_type i=0; i<devices.size(); i++) {
2341       cout << i << ".  \"" << devices[i] << "\"" << endl;
2342     }
2343     devices.clear();
2344     smgr.stop();
2345     exit(0);
2346   } else if (result == FG_OPTIONS_EXIT) {
2347     exit(0);
2348   }
2349 }
2350 
readConfig(const SGPath & path)2351 void Options::readConfig(const SGPath& path)
2352 {
2353   sg_gzifstream in( path );
2354   if ( !in.is_open() ) {
2355     return;
2356   }
2357 
2358   SG_LOG( SG_GENERAL, SG_INFO, "Processing config file: " << path );
2359 
2360   in >> skipcomment;
2361   while ( ! in.eof() ) {
2362     string line;
2363     getline( in, line, '\n' );
2364 
2365     // catch extraneous (DOS) line ending character
2366     int i;
2367     for (i = line.length(); i > 0; i--)
2368       if (line[i - 1] > 32)
2369         break;
2370     line = line.substr( 0, i );
2371 
2372     if ( parseOption(line, /* fromConfigFile */ true) == FG_OPTIONS_ERROR ) {
2373       cerr << endl << "Config file parse error: " << path << " '"
2374       << line << "'" << endl;
2375 	    p->showHelp = true;
2376     }
2377     in >> skipcomment;
2378   }
2379 
2380   p->insertGroupMarker(); // each config file is a group
2381 }
2382 
parseOption(const string & s,bool fromConfigFile)2383 int Options::parseOption(const string& s, bool fromConfigFile)
2384 {
2385   if ((s == "--help") || (s=="-h")) {
2386     return FG_OPTIONS_HELP;
2387   } else if ( (s == "--verbose") || (s == "-v") ) {
2388     // verbose help/usage request
2389     return FG_OPTIONS_VERBOSE_HELP;
2390   } else if ((s == "--console") || (s == "-c")) {
2391       fgOptConsole(nullptr);
2392 	  return FG_OPTIONS_OK;
2393   } else if (s.find("-psn") == 0) {
2394     // on Mac, when launched from the GUI, we are passed the ProcessSerialNumber
2395     // as an argument (and no others). Silently ignore the argument here.
2396     return FG_OPTIONS_OK;
2397   } else if ( s.find( "--show-aircraft") == 0) {
2398     return(FG_OPTIONS_SHOW_AIRCRAFT);
2399   } else if ( s.find( "--show-sound-devices") == 0) {
2400     return(FG_OPTIONS_SHOW_SOUND_DEVICES);
2401   } else if ( s.find( "--no-default-config") == 0) {
2402     return FG_OPTIONS_NO_DEFAULT_CONFIG;
2403   } else if ( s.find( "--prop:") == 0) {
2404     // property setting has a slightly different syntax, so fudge things
2405     OptionDesc* desc = p->findOption("prop");
2406     if (s.find("=", 7) == string::npos) { // no equals token
2407       SG_LOG(SG_GENERAL, SG_ALERT, "malformed property option:" << s);
2408       return FG_OPTIONS_ERROR;
2409     }
2410 
2411     p->values.push_back(OptionValue(desc, s.substr(7)));
2412     return FG_OPTIONS_OK;
2413   } else if ( s.find("--config=") == 0) {
2414     SGPath path = s.substr(9);
2415     if (path.extension() == "xml") {
2416         p->propertyFiles.push_back(path);
2417     } else if (fromConfigFile) {
2418       flightgear::fatalMessageBoxThenExit(
2419         "FlightGear",
2420         "Invalid use of the --config option.",
2421         "Sorry, it is currently not supported to load a configuration file "
2422         "using --config from another configuration file.\n\n"
2423         "Note: this does not apply to loading of XML PropertyList files "
2424         "with --config.");
2425     } else {                // the --config option comes from the command line
2426         p->configFiles.push_back(path);
2427     }
2428 
2429     return FG_OPTIONS_OK;
2430   } else if ( s.find( "--" ) == 0 ) {
2431     size_t eqPos = s.find( '=' );
2432     string key, value;
2433     if (eqPos == string::npos) {
2434       key = s.substr(2);
2435     } else {
2436       key = s.substr( 2, eqPos - 2 );
2437       value = s.substr( eqPos + 1);
2438     }
2439 
2440     return addOption(key, value);
2441   } else {
2442       flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + s);
2443     return FG_OPTIONS_ERROR;
2444   }
2445 }
2446 
addOption(const string & key,const string & value)2447 int Options::addOption(const string &key, const string &value)
2448 {
2449     if (key == "config") {
2450         // occurs when the launcher adds --config options
2451         SGPath path(value);
2452         if (!path.exists()) {
2453             return FG_OPTIONS_ERROR;
2454         }
2455 
2456         if (path.extension() == "xml") {
2457             p->propertyFiles.push_back(path);
2458         } else {
2459             p->insertGroupMarker(); // begin a group for the config file
2460             readConfig(path);
2461         }
2462 
2463         return FG_OPTIONS_OK;
2464     }
2465 
2466   OptionDesc* desc = p->findOption(key);
2467   if (!desc) {
2468     flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + key);
2469     return FG_OPTIONS_ERROR;
2470   }
2471 
2472   if (!(desc->type & OPTION_MULTI)) {
2473     OptionValueVec::const_iterator it = p->findValue(key);
2474     if (it != p->values.end()) {
2475       SG_LOG(SG_GENERAL, SG_WARN, "multiple values forbidden for option:" << key << ", ignoring:" << value);
2476       return FG_OPTIONS_OK;
2477     }
2478   }
2479 
2480   p->values.push_back(OptionValue(desc, value));
2481   return FG_OPTIONS_OK;
2482 }
2483 
setOption(const string & key,const string & value)2484 int Options::setOption(const string &key, const string &value)
2485 {
2486     OptionDesc* desc = p->findOption(key);
2487     if (!desc) {
2488         flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + key);
2489         return FG_OPTIONS_ERROR;
2490     }
2491 
2492     if (!(desc->type & OPTION_MULTI)) {
2493         OptionValueVec::iterator it = p->findValue(key);
2494         if (it != p->values.end()) {
2495             // remove existing valye
2496             p->values.erase(it);
2497         }
2498     }
2499 
2500     p->values.push_back(OptionValue(desc, value));
2501     return FG_OPTIONS_OK;
2502 }
2503 
clearOption(const std::string & key)2504 void Options::clearOption(const std::string& key)
2505 {
2506     OptionValueVec::iterator it = p->findValue(key);
2507     for (; it != p->values.end(); it = p->findValue(key)) {
2508         p->values.erase(it);
2509     }
2510 }
2511 
isOptionSet(const string & key) const2512 bool Options::isOptionSet(const string &key) const
2513 {
2514   OptionValueVec::const_iterator it = p->findValue(key);
2515   return (it != p->values.end());
2516 }
2517 
valueForOption(const string & key,const string & defValue) const2518 string Options::valueForOption(const string& key, const string& defValue) const
2519 {
2520   OptionValueVec::const_iterator it = p->findValue(key);
2521   if (it == p->values.end()) {
2522     return defValue;
2523   }
2524 
2525   return it->value;
2526 }
2527 
valuesForOption(const std::string & key) const2528 string_list Options::valuesForOption(const std::string& key) const
2529 {
2530   string_list result;
2531   OptionValueVec::const_iterator it = p->values.begin();
2532   for (; it != p->values.end(); ++it) {
2533     if (!it->desc) {
2534       continue; // ignore marker values
2535     }
2536 
2537     if (it->desc->option == key) {
2538       result.push_back(it->value);
2539     }
2540   }
2541 
2542   return result;
2543 }
2544 
defaultDownloadDir()2545 SGPath defaultDownloadDir()
2546 {
2547 #if defined(SG_WINDOWS)
2548     const SGPath p = SGPath::home() / "FlightGear" / "Downloads";
2549     return p;
2550 #endif
2551     return globals->get_fg_home();
2552 }
2553 
actualDownloadDir()2554 SGPath Options::actualDownloadDir()
2555 {
2556     SGPath downloadDir = SGPath::fromUtf8(valueForOption("download-dir"));
2557     if (!downloadDir.isNull()) {
2558         return downloadDir;
2559     }
2560 
2561     return defaultDownloadDir();
2562 }
2563 
defaultTextureCacheDir()2564 SGPath defaultTextureCacheDir()
2565 {
2566     return Options::sharedInstance()->actualDownloadDir() / "TextureCache";
2567 }
2568 
processOptions()2569 OptionResult Options::processOptions()
2570 {
2571   // establish locale before showing help (this selects the default locale,
2572   // when no explicit option was set)
2573   globals->get_locale()->selectLanguage(valueForOption("language").c_str());
2574 
2575   // now FG_ROOT is setup, process various command line options that bail us
2576   // out quickly, but rely on aircraft / root settings
2577   if (p->showHelp) {
2578     showUsage();
2579     return FG_OPTIONS_EXIT;
2580   }
2581 
2582   // processing order is complicated. We must process groups LIFO, but the
2583   // values *within* each group in FIFO order, to retain consistency with
2584   // older versions of FG, and existing user configs.
2585   // in practice this means system.fgfsrc must be *processed* before
2586   // .fgfsrc, which must be processed before the command line args, and so on.
2587   OptionValueVec::const_iterator groupEnd = p->values.end();
2588 
2589   while (groupEnd != p->values.begin()) {
2590     OptionValueVec::const_iterator groupBegin = p->rfindGroup(groupEnd);
2591   // run over the group in FIFO order
2592     OptionValueVec::const_iterator it;
2593     for (it = groupBegin; it != groupEnd; ++it) {
2594       int result = p->processOption(it->desc, it->value);
2595       switch(result)
2596       {
2597           case FG_OPTIONS_ERROR:
2598               showUsage();
2599               return FG_OPTIONS_ERROR;
2600 
2601           case FG_OPTIONS_EXIT:
2602               return FG_OPTIONS_EXIT;
2603 
2604           default:
2605               break;
2606       }
2607         if (it->desc) {
2608             SG_LOG(SG_GENERAL, SG_INFO, "\toption:" << it->desc->option << " = " << it->value);
2609         }
2610     }
2611 
2612     groupEnd = groupBegin;
2613   }
2614 
2615   for (const SGPath& file : p->propertyFiles) {
2616     SG_LOG(SG_GENERAL, SG_INFO,
2617            "Reading command-line property file " << file);
2618 	  readProperties(file, globals->get_props());
2619   }
2620 
2621 // now options are process, do supplemental fixup
2622   const char *envp = ::getenv( "FG_SCENERY" );
2623   if (envp) {
2624       globals->append_fg_scenery(SGPath::pathsFromEnv("FG_SCENERY"));
2625   }
2626 
2627     // Download dir fix-up
2628     SGPath downloadDir = SGPath::fromUtf8(valueForOption("download-dir"));
2629     if (downloadDir.isNull()) {
2630         downloadDir = defaultDownloadDir();
2631         SG_LOG(SG_GENERAL, SG_INFO,
2632                "Using default download dir: " << downloadDir);
2633     } else {
2634       SG_LOG(SG_GENERAL, SG_INFO,
2635              "Using explicit download dir: " << downloadDir);
2636     }
2637 
2638     simgear::Dir d(downloadDir);
2639     if (!d.exists()) {
2640       SG_LOG(SG_GENERAL, SG_INFO,
2641              "Creating download dir: " << downloadDir);
2642       d.create(0755);
2643     }
2644 
2645     // This is safe because the value of 'downloadDir' is trustworthy. In
2646     // particular, it can't be influenced by Nasal code, not even indirectly
2647     // via a Nasal-writable place such as the property tree.
2648     globals->set_download_dir(downloadDir);
2649 
2650     // Texture Cache directory handling
2651     SGPath textureCacheDir = SGPath::fromUtf8(valueForOption("texture-cache-dir"));
2652     if (textureCacheDir.isNull()) {
2653         textureCacheDir = defaultTextureCacheDir();
2654         SG_LOG(SG_GENERAL, SG_INFO,
2655             "Using default texture cache directory: " << textureCacheDir);
2656     }
2657     else {
2658         SG_LOG(SG_GENERAL, SG_INFO,
2659             "Using explicit texture cache directory: " << textureCacheDir);
2660     }
2661 
2662     simgear::Dir tcd(textureCacheDir);
2663     if (!tcd.exists()) {
2664         SG_LOG(SG_GENERAL, SG_INFO,
2665             "Creating texture cache directory: " << textureCacheDir);
2666         tcd.create(0755);
2667     }
2668 
2669     globals->set_texture_cache_dir(textureCacheDir);
2670 
2671 
2672     // TerraSync directory fixup
2673     SGPath terrasyncDir = SGPath::fromUtf8(valueForOption("terrasync-dir"));
2674     if (terrasyncDir.isNull()) {
2675       terrasyncDir = downloadDir / "TerraSync";
2676       // No “default” qualifier here, because 'downloadDir' may be non-default
2677       SG_LOG(SG_GENERAL, SG_INFO,
2678              "Using TerraSync dir: " << terrasyncDir);
2679     } else {
2680       SG_LOG(SG_GENERAL, SG_INFO,
2681              "Using explicit TerraSync dir: " << terrasyncDir);
2682     }
2683 
2684     d = simgear::Dir(terrasyncDir);
2685     if (!d.exists()) {
2686       SG_LOG(SG_GENERAL, SG_INFO,
2687              "Creating TerraSync dir: " << terrasyncDir);
2688       d.create(0755);
2689     }
2690 
2691     // This is safe because the value of 'terrasyncDir' is trustworthy. In
2692     // particular, it can't be influenced by Nasal code, not even indirectly
2693     // via a Nasal-writable place such as the property tree.
2694     globals->set_terrasync_dir(terrasyncDir);
2695 
2696     // check if we setup a scenery path so far
2697     bool addFGDataScenery = globals->get_fg_scenery().empty();
2698 
2699     // always add the terrasync location, regardless of whether terrasync
2700     // is enabled or not. This allows us to toggle terrasync on/off at
2701     // runtime and have things work as expected
2702     const PathList& scenery_paths(globals->get_fg_scenery());
2703     if (std::find(scenery_paths.begin(), scenery_paths.end(), terrasyncDir) == scenery_paths.end()) {
2704         // terrasync dir is not in the scenery paths, add it
2705         globals->append_fg_scenery(terrasyncDir);
2706     }
2707 
2708     if (addFGDataScenery) {
2709         // no scenery paths set at all, use the data in FG_ROOT
2710         // ensure this path is added last
2711         SGPath root(globals->get_fg_root());
2712         root.append("Scenery");
2713         globals->append_fg_scenery(root);
2714     }
2715 
2716   if (isOptionSet("json-report")) {
2717     printJSONReport();
2718     return FG_OPTIONS_EXIT;
2719   } else if (isOptionSet("version")) {
2720     showVersion();
2721     return FG_OPTIONS_EXIT;
2722   }
2723 
2724   return FG_OPTIONS_OK;
2725 }
2726 
showUsage() const2727 void Options::showUsage() const
2728 {
2729   fgOptLogLevel( "alert" );
2730 
2731   FGLocale *locale = globals->get_locale();
2732   SGPropertyNode options_root;
2733 
2734   simgear::requestConsole(); // ensure console is shown on Windows
2735   cout << endl;
2736 
2737   try {
2738     fgLoadProps("options.xml", &options_root);
2739   } catch (const sg_exception &) {
2740     cout << "Unable to read the help file." << endl;
2741     cout << "Make sure the file options.xml is located in the FlightGear base directory," << endl;
2742     cout << "and the location of the base directory is specified by setting $FG_ROOT or" << endl;
2743     cout << "by adding --fg-root=path as a program argument." << endl;
2744 
2745     exit(-1);
2746   }
2747 
2748   SGPropertyNode *options = options_root.getNode("options");
2749   if (!options) {
2750     SG_LOG( SG_GENERAL, SG_ALERT,
2751            "Error reading options.xml: <options> element not found." );
2752     exit(-1);
2753   }
2754 
2755   if (!locale->loadResource("options"))
2756   {
2757       cout << "Unable to read the language resource." << endl;
2758       exit(-1);
2759   }
2760 
2761   std::string usage = locale->getLocalizedString(options->getStringValue("usage"), "options");
2762   if (!usage.empty()) {
2763     cout << usage << endl;
2764   }
2765 
2766   vector<SGPropertyNode_ptr>section = options->getChildren("section");
2767   for (unsigned int j = 0; j < section.size(); j++) {
2768     string msg = "";
2769 
2770     vector<SGPropertyNode_ptr>option = section[j]->getChildren("option");
2771     for (unsigned int k = 0; k < option.size(); k++) {
2772 
2773       SGPropertyNode *name = option[k]->getNode("name");
2774       SGPropertyNode *short_name = option[k]->getNode("short");
2775       SGPropertyNode *key = option[k]->getNode("key");
2776       SGPropertyNode *arg = option[k]->getNode("arg");
2777       bool brief = option[k]->getNode("brief") != 0;
2778 
2779       if ((brief || p->verbose) && name) {
2780         string tmp = name->getStringValue();
2781 
2782         if (key){
2783           tmp.append(":");
2784           tmp.append(key->getStringValue());
2785         }
2786         if (arg) {
2787           tmp.append("=");
2788           tmp.append(arg->getStringValue());
2789         }
2790         if (short_name) {
2791           tmp.append(", -");
2792           tmp.append(short_name->getStringValue());
2793         }
2794 
2795         if (tmp.size() <= 25) {
2796           msg+= "   --";
2797           msg += tmp;
2798           msg.append( 27-tmp.size(), ' ');
2799         } else {
2800           msg += "\n   --";
2801           msg += tmp + '\n';
2802           msg.append(32, ' ');
2803         }
2804         // There may be more than one <description> tag associated
2805         // with one option
2806 
2807         vector<SGPropertyNode_ptr> desc;
2808         desc = option[k]->getChildren("description");
2809         if (! desc.empty()) {
2810           for ( unsigned int l = 0; l < desc.size(); l++) {
2811             string t = desc[l]->getStringValue();
2812 
2813             // There may be more than one translation line.
2814             vector<SGPropertyNode_ptr>trans_desc = locale->getLocalizedStrings(t.c_str(),"options");
2815             for ( unsigned int m = 0; m < trans_desc.size(); m++ ) {
2816               string t_str = trans_desc[m]->getStringValue();
2817 
2818               if ((m > 0) || ((l > 0) && m == 0)) {
2819                 msg.append( 32, ' ');
2820               }
2821 
2822               // If the string is too large to fit on the screen,
2823               // then split it up in several pieces.
2824 
2825               while ( t_str.size() > 47 ) {
2826 
2827                 string::size_type m = t_str.rfind(' ', 47);
2828                 msg += t_str.substr(0, m) + '\n';
2829                 msg.append( 32, ' ');
2830 
2831                 t_str.erase(t_str.begin(), t_str.begin() + m + 1);
2832               }
2833               msg += t_str + '\n';
2834             }
2835           }
2836         }
2837       }
2838     }
2839 
2840     std::string name = locale->getLocalizedString(section[j]->getStringValue("name"),"options");
2841     if (!msg.empty() && !name.empty()) {
2842       cout << endl << name << ":" << endl;
2843       cout << msg;
2844       msg.erase();
2845     }
2846   }
2847 
2848   if ( !p->verbose ) {
2849     std::string verbose_help = locale->getLocalizedString(options->getStringValue("verbose-help"),"options");
2850     if (!verbose_help.empty())
2851         cout << endl << verbose_help << endl;
2852   }
2853 #ifdef _MSC_VER
2854   std::cout << "Hit a key to continue..." << std::endl;
2855   std::cin.get();
2856 #endif
2857 }
2858 
showVersion() const2859 void Options::showVersion() const
2860 {
2861     cout << "FlightGear version: " << FLIGHTGEAR_VERSION << endl;
2862     cout << "Revision: " << REVISION << endl;
2863     cout << "Build-Id: " << JENKINS_BUILD_ID << endl;
2864     cout << "Build-Type: " << FG_BUILD_TYPE << endl;
2865     cout << "FG_ROOT=" << globals->get_fg_root().utf8Str() << endl;
2866     cout << "FG_HOME=" << globals->get_fg_home().utf8Str() << endl;
2867     cout << "FG_SCENERY=";
2868 
2869     PathList scn = globals->get_fg_scenery();
2870     cout << SGPath::join(scn, SGPath::pathListSep) << endl;
2871     cout << "SimGear version: " << SG_STRINGIZE(SIMGEAR_VERSION) << endl;
2872     cout << "OSG version: " << osgGetVersion() << endl;
2873     cout << "PLIB version: " << PLIB_VERSION << endl;
2874 }
2875 
2876 // Print a report using JSON syntax on the standard output, encoded in UTF-8.
2877 //
2878 // The report format is versioned, don't forget to update it when making
2879 // changes (see below).
printJSONReport() const2880 void Options::printJSONReport() const
2881 {
2882   cJSON *rootNode = cJSON_CreateObject();
2883 
2884   cJSON *metaNode = cJSON_CreateObject();
2885   cJSON_AddItemToObject(rootNode, "meta", metaNode);
2886   cJSON_AddStringToObject(metaNode, "type", "FlightGear JSON report");
2887   // When making compatible changes to the format (e.g., adding members to
2888   // JSON objects), only the minor version number should be increased.
2889   // Increase the major version number when a change is backward-incompatible
2890   // (such as the removal, renaming or semantic change of a member). Of
2891   // course, incompatible changes should only be considered as a last
2892   // recourse.
2893   cJSON_AddNumberToObject(metaNode, "format major version", 1);
2894   cJSON_AddNumberToObject(metaNode, "format minor version", 0);
2895 
2896   cJSON *generalNode = cJSON_CreateObject();
2897   cJSON_AddItemToObject(rootNode, "general", generalNode);
2898   cJSON_AddStringToObject(generalNode, "name", "FlightGear");
2899   cJSON_AddStringToObject(generalNode, "version", FLIGHTGEAR_VERSION);
2900   cJSON_AddStringToObject(generalNode, "build ID", JENKINS_BUILD_ID);
2901   cJSON_AddStringToObject(generalNode, "build type", FG_BUILD_TYPE);
2902 
2903   cJSON *configNode = cJSON_CreateObject();
2904   cJSON_AddItemToObject(rootNode, "config", configNode);
2905   cJSON_AddStringToObject(configNode, "FG_ROOT",
2906                           globals->get_fg_root().utf8Str().c_str());
2907   cJSON_AddStringToObject(configNode, "FG_HOME",
2908                           globals->get_fg_home().utf8Str().c_str());
2909 
2910   cJSON *sceneryPathsNode = p->createJSONArrayFromPathList(globals->get_fg_scenery());
2911   cJSON_AddItemToObject(configNode, "scenery paths", sceneryPathsNode);
2912 
2913   cJSON *aircraftPathsNode = p->createJSONArrayFromPathList(
2914     globals->get_aircraft_paths());
2915   cJSON_AddItemToObject(configNode, "aircraft paths", aircraftPathsNode);
2916 
2917   cJSON_AddStringToObject(configNode, "TerraSync directory",
2918                           globals->get_terrasync_dir().utf8Str().c_str());
2919 
2920   cJSON_AddStringToObject(configNode, "download directory",
2921                           globals->get_download_dir().utf8Str().c_str());
2922 
2923   cJSON_AddStringToObject(configNode, "autosave file",
2924                           globals->autosaveFilePath().utf8Str().c_str());
2925 
2926   // Get the ordered lists of apt.dat, fix.dat and nav.dat files used by the
2927   // NavCache
2928   NavDataCache* cache = NavDataCache::instance();
2929   if (!cache) {
2930     cache = NavDataCache::createInstance();
2931   }
2932 
2933   cJSON *navDataNode = cJSON_CreateObject();
2934   cJSON_AddItemToObject(rootNode, "navigation data", navDataNode);
2935 
2936   // Write each list to the JSON tree
2937   for (const auto& datType: {NavDataCache::DATFILETYPE_APT,
2938                              NavDataCache::DATFILETYPE_FIX,
2939                              NavDataCache::DATFILETYPE_NAV}) {
2940     // For this method, it doesn't matter if the cache is out-of-date
2941     const NavDataCache::DatFilesGroupInfo& datFilesInfo =
2942       cache->getDatFilesInfo(datType);
2943     cJSON *datPathsNode = p->createJSONArrayFromPathList(datFilesInfo.paths);
2944     string key = NavDataCache::datTypeStr[datType] + ".dat files";
2945     cJSON_AddItemToObject(navDataNode, key.c_str(), datPathsNode);
2946   }
2947 
2948   // Print the JSON tree to the standard output
2949   char *report = cJSON_Print(rootNode);
2950   cout << report << endl;
2951   cJSON_Delete(rootNode);
2952 }
2953 
2954 #if defined(__CYGWIN__)
platformDefaultRoot() const2955 SGPath Options::platformDefaultRoot() const
2956 {
2957   return SGPath::fromUtf8("../data");
2958 }
2959 
2960 #elif defined(SG_WINDOWS)
platformDefaultRoot() const2961 SGPath Options::platformDefaultRoot() const
2962 {
2963   return SGPath::fromUtf8("..\\data");
2964 }
2965 #elif defined(SG_MAC)
2966 // platformDefaultRoot defined in CocoaHelpers.mm
2967 #else
platformDefaultRoot() const2968 SGPath Options::platformDefaultRoot() const
2969 {
2970     return SGPath::fromUtf8(PKGLIBDIR);
2971 }
2972 
2973 #endif
2974 
extractOptions() const2975 string_list Options::extractOptions() const
2976 {
2977     string_list result;
2978     for (auto opt : p->values) {
2979         if (opt.desc == nullptr) {
2980             continue;
2981         }
2982 
2983         if (!strcmp(opt.desc->option,"prop")) {
2984             result.push_back("prop:" + opt.value);
2985         } else if (opt.value.empty()) {
2986             result.push_back(opt.desc->option);
2987         } else {
2988             result.push_back(std::string(opt.desc->option) + "=" + opt.value);
2989         }
2990     }
2991 
2992     return result;
2993 }
2994 
setupRoot(int argc,char ** argv)2995 OptionResult Options::setupRoot(int argc, char** argv)
2996 {
2997     SGPath root(globals->get_fg_root());
2998     bool usingDefaultRoot = false;
2999 
3000     // root has already been set, so skip the fg_root setting and validation.
3001     if (!root.isNull()) {
3002         return FG_OPTIONS_OK;
3003     }
3004 
3005   if (isOptionSet("fg-root")) {
3006       root = SGPath::fromUtf8(valueForOption("fg-root")); // easy!
3007       SG_LOG(SG_GENERAL, SG_INFO, "set from command-line argument: fg_root = " << root );
3008   } else {
3009   // Next check if fg-root is set as an env variable
3010     char *envp = ::getenv( "FG_ROOT" );
3011     if ( envp != nullptr ) {
3012         root = SGPath::fromEnv("FG_ROOT");
3013         SG_LOG(SG_GENERAL, SG_INFO, "set from FG_ROOT env var: fg_root = " << root );
3014     } else {
3015 #if defined(HAVE_QT)
3016         auto restoreResult = SetupRootDialog::restoreUserSelectedRoot(root);
3017         if (restoreResult == SetupRootDialog::UserExit) {
3018             return FG_OPTIONS_EXIT;
3019         } else if (restoreResult == SetupRootDialog::UseDefault) {
3020             root = SGPath{}; // clear any value, so we fall through in root.isNull() below
3021         }
3022 #endif
3023 
3024         if (root.isNull()) {
3025             usingDefaultRoot = true;
3026             root = platformDefaultRoot();
3027             SG_LOG(SG_GENERAL, SG_INFO, "platform default fg_root = " << root );
3028         } else {
3029             SG_LOG(SG_GENERAL, SG_INFO, "Qt launcher set fg_root = " << root );
3030         }
3031     }
3032   }
3033 
3034   globals->set_fg_root(root);
3035     string base_version = fgBasePackageVersion(root);
3036 
3037 
3038 #if defined(HAVE_QT)
3039     // only compare major and minor version, not the patch level.
3040     const int versionComp = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, base_version, 2);
3041 
3042     // note we never end up here if restoring a user selected root via
3043     // the Qt GUI, since that code pre-validates the path. But if we're using
3044     // a command-line, env-var or default root this check can fail and
3045     // we still want to use the GUI in that case
3046     if (versionComp != 0) {
3047         flightgear::initApp(argc, argv);
3048         bool ok = SetupRootDialog::runDialog(usingDefaultRoot);
3049         if (!ok) {
3050             return FG_OPTIONS_EXIT;
3051         }
3052     }
3053 #else
3054     SG_UNUSED(usingDefaultRoot);
3055 
3056     // validate it
3057     if (base_version.empty()) {
3058         flightgear::fatalMessageBoxThenExit(
3059           "Base package not found",
3060           "Required data files not found, please check your installation.",
3061           "Looking for base-package files at: '" + root.str() + "'");
3062     }
3063 
3064     // only compare major and minor version, not the patch level.
3065     const int versionComp = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, base_version, 2);
3066     if (versionComp != 0) {
3067       flightgear::fatalMessageBoxThenExit(
3068         "Base package version mismatch",
3069         "Version check failed, please check your installation.",
3070         "Found data files for version '" + base_version + "' at '" +
3071         globals->get_fg_root().str() + "', version '" +
3072         std::string(FLIGHTGEAR_VERSION) + "' is required.");
3073   }
3074 #endif
3075     return FG_OPTIONS_OK;
3076 }
3077 
shouldLoadDefaultConfig() const3078 bool Options::shouldLoadDefaultConfig() const
3079 {
3080   return p->shouldLoadDefaultConfig;
3081 }
3082 
setShouldLoadDefaultConfig(bool load)3083 void Options::setShouldLoadDefaultConfig(bool load)
3084 {
3085     p->shouldLoadDefaultConfig = load;
3086 }
3087 
checkForArg(int argc,char * argv[],const char * checkArg)3088 bool Options::checkForArg(int argc, char* argv[], const char* checkArg)
3089 {
3090     for (int i = 0; i < argc; ++i) {
3091         char* arg = argv[i];
3092         if (arg == nullptr) {
3093             continue;
3094         }
3095 
3096         if (*arg != '-') { // we only care about args with a leading hypen
3097             continue;
3098         }
3099 
3100         arg++;
3101         if (*arg == '-') { // skip double hypens
3102             arg++;
3103         }
3104 
3105         if (strcmp(arg, checkArg) == 0) {
3106             return true;
3107         }
3108     }
3109 
3110     return false;
3111 }
3112 
getArgValue(int argc,char * argv[],const char * checkArg)3113 std::string Options::getArgValue(int argc, char* argv[], const char* checkArg)
3114 {
3115     const auto len = strlen(checkArg);
3116     for (int i = 0; i < argc; ++i) {
3117         char* arg = argv[i];
3118         if (arg == nullptr) {
3119             continue;
3120         }
3121 
3122         if (strncmp(arg, checkArg, len) == 0) {
3123             const auto alen = strlen(arg);
3124             if ((alen - len) < 2)
3125                 return {}; // no value after the =, or missing = entirely
3126             return std::string(arg + len + 1);
3127         }
3128     } // of args iteration
3129 
3130     return {};
3131 }
3132 
3133 } // of namespace flightgear
3134