1 /*
2 * Copyright (C) 2002-2021 The DOSBox Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "setup.h"
20
21 #include <algorithm>
22 #include <climits>
23 #include <cstdlib>
24 #include <fstream>
25 #include <limits>
26 #include <regex>
27 #include <sstream>
28 #include <string_view>
29
30 #include "control.h"
31 #include "string_utils.h"
32 #include "support.h"
33 #include "cross.h"
34
35 #if defined(_MSC_VER) || (defined(__MINGW32__) && defined(__clang__))
36 _CRTIMP extern char **_environ;
37 #else
38 extern char **environ;
39 #endif
40
41 using namespace std;
42 static std::string current_config_dir; // Set by parseconfigfile so Prop_path can use it to construct the realpath
destroy()43 void Value::destroy() throw(){
44 if (type == V_STRING) delete _string;
45 }
46
copy(Value const & in)47 Value& Value::copy(Value const& in) {
48 if (this != &in) { //Selfassigment!
49 if(type != V_NONE && type != in.type) throw WrongType();
50 destroy();
51 plaincopy(in);
52 }
53 return *this;
54 }
55
plaincopy(Value const & in)56 void Value::plaincopy(Value const& in) throw(){
57 type = in.type;
58 _int = in._int;
59 _double = in._double;
60 _bool = in._bool;
61 _hex = in._hex;
62 if(type == V_STRING) _string = new string(*in._string);
63 }
64
operator bool() const65 Value::operator bool () const {
66 if(type != V_BOOL) throw WrongType();
67 return _bool;
68 }
69
operator Hex() const70 Value::operator Hex () const {
71 if(type != V_HEX) throw WrongType();
72 return _hex;
73 }
74
operator int() const75 Value::operator int () const {
76 if(type != V_INT) throw WrongType();
77 return _int;
78 }
79
operator double() const80 Value::operator double () const {
81 if(type != V_DOUBLE) throw WrongType();
82 return _double;
83 }
84
operator char const*() const85 Value::operator char const* () const {
86 if(type != V_STRING) throw WrongType();
87 return _string->c_str();
88 }
89
operator ==(Value const & other) const90 bool Value::operator==(Value const& other) const {
91 if(this == &other) return true;
92 if(type != other.type) return false;
93 switch(type){
94 case V_BOOL:
95 if(_bool == other._bool) return true;
96 break;
97 case V_INT:
98 if(_int == other._int) return true;
99 break;
100 case V_HEX:
101 if(_hex == other._hex) return true;
102 break;
103 case V_DOUBLE:
104 if(_double == other._double) return true;
105 break;
106 case V_STRING:
107 if((*_string) == (*other._string)) return true;
108 break;
109 default:
110 E_Exit("comparing stuff that doesn't make sense");
111 break;
112 }
113 return false;
114 }
SetValue(string const & in,Etype _type)115 bool Value::SetValue(string const& in,Etype _type) {
116 /* Throw exception if the current type isn't the wanted type
117 * Unless the wanted type is current.
118 */
119 if(_type == V_CURRENT && type == V_NONE) throw WrongType();
120 if(_type != V_CURRENT) {
121 if(type != V_NONE && type != _type) throw WrongType();
122 type = _type;
123 }
124 bool retval = true;
125 switch(type){
126 case V_HEX:
127 retval = set_hex(in);
128 break;
129 case V_INT:
130 retval = set_int(in);
131 break;
132 case V_BOOL:
133 retval = set_bool(in);
134 break;
135 case V_STRING:
136 set_string(in);
137 break;
138 case V_DOUBLE:
139 retval = set_double(in);
140 break;
141
142 case V_NONE:
143 case V_CURRENT:
144 default:
145 /* Shouldn't happen!/Unhandled */
146 throw WrongType();
147 break;
148 }
149 return retval;
150 }
151
set_hex(std::string const & in)152 bool Value::set_hex(std::string const& in) {
153 istringstream input(in);
154 input.flags(ios::hex);
155 int result = INT_MIN;
156 input >> result;
157 if(result == INT_MIN) return false;
158 _hex = result;
159 return true;
160 }
161
set_int(string const & in)162 bool Value::set_int(string const &in) {
163 istringstream input(in);
164 int result = INT_MIN;
165 input >> result;
166 if(result == INT_MIN) return false;
167 _int = result;
168 return true;
169 }
set_double(string const & in)170 bool Value::set_double(string const &in) {
171 istringstream input(in);
172 double result = std::numeric_limits<double>::infinity();
173 input >> result;
174 if(result == std::numeric_limits<double>::infinity()) return false;
175 _double = result;
176 return true;
177 }
178
set_bool(string const & in)179 bool Value::set_bool(string const &in) {
180 istringstream input(in);
181 string result;
182 input >> result;
183 lowcase(result);
184 _bool = true; // TODO
185 if(!result.size()) return false;
186
187 if(result=="0" || result=="disabled" || result=="false" || result=="off") {
188 _bool = false;
189 } else if(result=="1" || result=="enabled" || result=="true" || result=="on") {
190 _bool = true;
191 } else return false;
192
193 return true;
194 }
195
set_string(string const & in)196 void Value::set_string(string const & in) {
197 if(!_string) _string = new string();
198 _string->assign(in);
199 }
200
ToString() const201 string Value::ToString() const {
202 ostringstream oss;
203 switch(type) {
204 case V_HEX:
205 oss.flags(ios::hex);
206 oss << _hex;
207 break;
208 case V_INT:
209 oss << _int;
210 break;
211 case V_BOOL:
212 oss << boolalpha << _bool;
213 break;
214 case V_STRING:
215 oss << *_string;
216 break;
217 case V_DOUBLE:
218 oss.precision(2);
219 oss << fixed << _double;
220 break;
221 case V_NONE:
222 case V_CURRENT:
223 default:
224 E_Exit("ToString messed up ?");
225 break;
226 }
227 return oss.str();
228 }
229
Property(const std::string & name,Changeable::Value when)230 Property::Property(const std::string &name, Changeable::Value when)
231 : propname(name),
232 value(),
233 suggested_values{},
234 default_value(),
235 change(when)
236 {
237 assertm(std::regex_match(name, std::regex{"[a-zA-Z0-9_]+"}),
238 "Only letters, digits, and underscores are allowed in property name");
239 }
240
CheckValue(Value const & in,bool warn)241 bool Property::CheckValue(Value const& in, bool warn){
242 if (suggested_values.empty()) return true;
243 for(const_iter it = suggested_values.begin();it != suggested_values.end();++it) {
244 if ( (*it) == in) { //Match!
245 return true;
246 }
247 }
248 if (warn) LOG_MSG("\"%s\" is not a valid value for variable: %s.\nIt might now be reset to the default value: %s",in.ToString().c_str(),propname.c_str(),default_value.ToString().c_str());
249 return false;
250 }
251
Set_help(string const & in)252 void Property::Set_help(string const& in) {
253 string result = string("CONFIG_") + propname;
254 upcase(result);
255 MSG_Add(result.c_str(),in.c_str());
256 }
257
GetHelp() const258 const char * Property::GetHelp() const
259 {
260 std::string result = "CONFIG_" + propname;
261 upcase(result);
262 return MSG_Get(result.c_str());
263 }
264
SetVal(Value const & in,bool forced,bool warn)265 bool Prop_int::SetVal(Value const& in, bool forced, bool warn) {
266 if (forced) {
267 value = in;
268 return true;
269 } else if (!suggested_values.empty()){
270 if ( CheckValue(in,warn) ) {
271 value = in;
272 return true;
273 } else {
274 value = default_value;
275 return false;
276 }
277 } else {
278 // Handle ranges if specified
279 const int mi = min_value;
280 const int ma = max_value;
281 int va = static_cast<int>(Value(in));
282
283 //No ranges
284 if (mi == -1 && ma == -1) { value = in; return true;}
285
286 //Inside range
287 if (va >= mi && va <= ma) { value = in; return true;}
288
289 //Outside range, set it to the closest boundary
290 if (va > ma ) va = ma; else va = mi;
291
292 if (warn) {
293 LOG_MSG("%s is outside the allowed range %s-%s for variable: %s.\n"
294 "It has been set to the closest boundary: %d.",
295 in.ToString().c_str(),
296 min_value.ToString().c_str(),
297 max_value.ToString().c_str(),
298 propname.c_str(),
299 va);
300 }
301
302 value = va;
303 return true;
304 }
305 }
CheckValue(Value const & in,bool warn)306 bool Prop_int::CheckValue(Value const& in, bool warn) {
307 // if(!suggested_values.empty() && Property::CheckValue(in,warn)) return true;
308 if(!suggested_values.empty()) return Property::CheckValue(in,warn);
309 // LOG_MSG("still used ?");
310 //No >= and <= in Value type and == is ambigious
311 const int mi = min_value;
312 const int ma = max_value;
313 int va = static_cast<int>(Value(in));
314 if (mi == -1 && ma == -1) return true;
315 if (va >= mi && va <= ma) return true;
316
317 if (warn) {
318 LOG_MSG("%s lies outside the range %s-%s for variable: %s.\n"
319 "It might now be reset to the default value: %s",
320 in.ToString().c_str(),
321 min_value.ToString().c_str(),
322 max_value.ToString().c_str(),
323 propname.c_str(),
324 default_value.ToString().c_str());
325 }
326 return false;
327 }
328
SetValue(std::string const & input)329 bool Prop_double::SetValue(std::string const& input) {
330 Value val;
331 if(!val.SetValue(input,Value::V_DOUBLE)) return false;
332 return SetVal(val,false,true);
333 }
334
335 //void Property::SetValue(char* input){
336 // value.SetValue(input, Value::V_CURRENT);
337 //}
SetValue(std::string const & input)338 bool Prop_int::SetValue(std::string const& input) {
339 Value val;
340 if (!val.SetValue(input,Value::V_INT)) return false;
341 bool retval = SetVal(val,false,true);
342 return retval;
343 }
344
SetValue(std::string const & input)345 bool Prop_string::SetValue(std::string const& input) {
346 //Special version for lowcase stuff
347 std::string temp(input);
348 //suggested values always case insensitive.
349 //If there are none then it can be paths and such which are case sensitive
350 if (!suggested_values.empty()) lowcase(temp);
351 Value val(temp,Value::V_STRING);
352 return SetVal(val,false,true);
353 }
CheckValue(Value const & in,bool warn)354 bool Prop_string::CheckValue(Value const& in, bool warn) {
355 if (suggested_values.empty()) return true;
356 for (const auto &val : suggested_values) {
357 if (val == in) { // Match!
358 return true;
359 }
360 if (val.ToString() == "%u") {
361 unsigned int v;
362 if (sscanf(in.ToString().c_str(), "%u", &v) == 1) {
363 return true;
364 }
365 }
366 }
367 if (warn) LOG_MSG("\"%s\" is not a valid value for variable: %s.\nIt might now be reset to the default value: %s",in.ToString().c_str(),propname.c_str(),default_value.ToString().c_str());
368 return false;
369 }
370
SetValue(std::string const & input)371 bool Prop_path::SetValue(std::string const& input) {
372 //Special version to merge realpath with it
373
374 Value val(input,Value::V_STRING);
375 bool retval = SetVal(val,false,true);
376
377 if (input.empty()) {
378 realpath.clear();
379 return false;
380 }
381 std::string workcopy(input);
382 Cross::ResolveHomedir(workcopy); //Parse ~ and friends
383 //Prepend config directory in it exists. Check for absolute paths later
384 if ( current_config_dir.empty()) realpath = workcopy;
385 else realpath = current_config_dir + CROSS_FILESPLIT + workcopy;
386 //Absolute paths
387 if (Cross::IsPathAbsolute(workcopy)) realpath = workcopy;
388 return retval;
389 }
390
SetValue(std::string const & input)391 bool Prop_bool::SetValue(std::string const& input) {
392 return value.SetValue(input,Value::V_BOOL);
393 }
394
SetValue(std::string const & input)395 bool Prop_hex::SetValue(std::string const& input) {
396 Value val;
397 val.SetValue(input,Value::V_HEX);
398 return SetVal(val,false,true);
399 }
400
make_default_value()401 void Prop_multival::make_default_value()
402 {
403 Property *p = section->Get_prop(0);
404 if (!p) return;
405
406 int i = 1;
407 std::string result = p->Get_Default_Value().ToString();
408 while( (p = section->Get_prop(i++)) ) {
409 std::string props = p->Get_Default_Value().ToString();
410 if (props.empty()) continue;
411 result += separator; result += props;
412 }
413 Value val(result,Value::V_STRING);
414 SetVal(val,false,true);
415 }
416
417 //TODO checkvalue stuff
SetValue(std::string const & input)418 bool Prop_multival_remain::SetValue(std::string const& input) {
419 Value val(input,Value::V_STRING);
420 bool retval = SetVal(val,false,true);
421
422 std::string local(input);
423 int i = 0,number_of_properties = 0;
424 Property *p = section->Get_prop(0);
425 //No properties in this section. do nothing
426 if (!p) return false;
427
428 while( (section->Get_prop(number_of_properties)) )
429 number_of_properties++;
430
431 string::size_type loc = string::npos;
432 while( (p = section->Get_prop(i++)) ) {
433 //trim leading separators
434 loc = local.find_first_not_of(separator);
435 if (loc != string::npos) local.erase(0,loc);
436 loc = local.find_first_of(separator);
437 string in = "";//default value
438 /* when i == number_of_properties add the total line. (makes more then
439 * one string argument possible for parameters of cpu) */
440 if (loc != string::npos && i < number_of_properties) { //separator found
441 in = local.substr(0,loc);
442 local.erase(0,loc+1);
443 } else if (local.size()) { //last argument or last property
444 in = local;
445 local.clear();
446 }
447 //Test Value. If it fails set default
448 Value valtest (in,p->Get_type());
449 if (!p->CheckValue(valtest,true)) {
450 make_default_value();
451 return false;
452 }
453 p->SetValue(in);
454 }
455 return retval;
456 }
457
458 //TODO checkvalue stuff
SetValue(std::string const & input)459 bool Prop_multival::SetValue(std::string const& input) {
460 Value val(input,Value::V_STRING);
461 bool retval = SetVal(val,false,true);
462
463 std::string local(input);
464 int i = 0;
465 Property *p = section->Get_prop(0);
466 //No properties in this section. do nothing
467 if (!p) return false;
468 Value::Etype prevtype = Value::V_NONE;
469 string prevargument = "";
470
471 string::size_type loc = string::npos;
472 while( (p = section->Get_prop(i++)) ) {
473 //trim leading separators
474 loc = local.find_first_not_of(separator);
475 if (loc != string::npos) local.erase(0,loc);
476 loc = local.find_first_of(separator);
477 string in = "";//default value
478 if (loc != string::npos) { //separator found
479 in = local.substr(0,loc);
480 local.erase(0,loc+1);
481 } else if (local.size()) { //last argument
482 in = local;
483 local.clear();
484 }
485
486 if (p->Get_type() == Value::V_STRING) {
487 //Strings are only checked against the suggested values list.
488 //Test Value. If it fails set default
489 Value valtest (in,p->Get_type());
490 if (!p->CheckValue(valtest,true)) {
491 make_default_value();
492 return false;
493 }
494 p->SetValue(in);
495 } else {
496 //Non-strings can have more things, conversion alone is not enough (as invalid values as converted to 0)
497 bool r = p->SetValue(in);
498 if (!r) {
499 if (in.empty() && p->Get_type() == prevtype ) {
500 //Nothing there, but same type of variable, so repeat it (sensitivity)
501 in = prevargument;
502 p->SetValue(in);
503 } else {
504 //Something was there to be parsed or not the same type. Invalidate entire property.
505 make_default_value();
506 }
507 }
508 }
509 prevtype = p->Get_type();
510 prevargument = in;
511
512 }
513 return retval;
514 }
515
GetValues() const516 const std::vector<Value>& Property::GetValues() const {
517 return suggested_values;
518 }
GetValues() const519 const std::vector<Value>& Prop_multival::GetValues() const {
520 Property *p = section->Get_prop(0);
521 //No properties in this section. do nothing
522 if (!p) return suggested_values;
523 int i =0;
524 while( (p = section->Get_prop(i++)) ) {
525 std::vector<Value> v = p->GetValues();
526 if(!v.empty()) return p->GetValues();
527 }
528 return suggested_values;
529 }
530
531 /*
532 void Section_prop::Add_double(char const * const _propname, double _value) {
533 Property* test=new Prop_double(_propname,_value);
534 properties.push_back(test);
535 }*/
536
Set_values(const char * const * in)537 void Property::Set_values(const char * const *in) {
538 Value::Etype type = default_value.type;
539 int i = 0;
540 while (in[i]) {
541 Value val(in[i],type);
542 suggested_values.push_back(val);
543 i++;
544 }
545 }
546
Set_values(const std::vector<std::string> & in)547 void Property::Set_values(const std::vector<std::string> & in) {
548 Value::Etype type = default_value.type;
549 for (auto &str : in) {
550 Value val(str, type);
551 suggested_values.push_back(val);
552 }
553 }
554
Add_int(string const & _propname,Property::Changeable::Value when,int _value)555 Prop_int* Section_prop::Add_int(string const& _propname, Property::Changeable::Value when, int _value) {
556 Prop_int* test=new Prop_int(_propname,when,_value);
557 properties.push_back(test);
558 return test;
559 }
560
Add_string(string const & _propname,Property::Changeable::Value when,char const * const _value)561 Prop_string* Section_prop::Add_string(string const& _propname, Property::Changeable::Value when, char const * const _value) {
562 Prop_string* test=new Prop_string(_propname,when,_value);
563 properties.push_back(test);
564 return test;
565 }
566
Add_path(string const & _propname,Property::Changeable::Value when,char const * const _value)567 Prop_path* Section_prop::Add_path(string const& _propname, Property::Changeable::Value when, char const * const _value) {
568 Prop_path* test=new Prop_path(_propname,when,_value);
569 properties.push_back(test);
570 return test;
571 }
572
Add_bool(string const & _propname,Property::Changeable::Value when,bool _value)573 Prop_bool* Section_prop::Add_bool(string const& _propname, Property::Changeable::Value when, bool _value) {
574 Prop_bool* test=new Prop_bool(_propname,when,_value);
575 properties.push_back(test);
576 return test;
577 }
578
Add_hex(string const & _propname,Property::Changeable::Value when,Hex _value)579 Prop_hex* Section_prop::Add_hex(string const& _propname, Property::Changeable::Value when, Hex _value) {
580 Prop_hex* test=new Prop_hex(_propname,when,_value);
581 properties.push_back(test);
582 return test;
583 }
584
Add_multi(std::string const & _propname,Property::Changeable::Value when,std::string const & sep)585 Prop_multival* Section_prop::Add_multi(std::string const& _propname, Property::Changeable::Value when,std::string const& sep) {
586 Prop_multival* test = new Prop_multival(_propname,when,sep);
587 properties.push_back(test);
588 return test;
589 }
590
Add_multiremain(std::string const & _propname,Property::Changeable::Value when,std::string const & sep)591 Prop_multival_remain* Section_prop::Add_multiremain(std::string const& _propname, Property::Changeable::Value when,std::string const& sep) {
592 Prop_multival_remain* test = new Prop_multival_remain(_propname,when,sep);
593 properties.push_back(test);
594 return test;
595 }
596
Get_int(string const & _propname) const597 int Section_prop::Get_int(string const&_propname) const {
598 for(const_it tel=properties.begin();tel!=properties.end();tel++){
599 if ((*tel)->propname==_propname){
600 return ((*tel)->GetValue());
601 }
602 }
603 return 0;
604 }
605
Get_bool(string const & _propname) const606 bool Section_prop::Get_bool(string const& _propname) const {
607 for(const_it tel = properties.begin();tel != properties.end();++tel){
608 if ((*tel)->propname == _propname){
609 return ((*tel)->GetValue());
610 }
611 }
612 return false;
613 }
614
Get_double(string const & _propname) const615 double Section_prop::Get_double(string const& _propname) const {
616 for(const_it tel = properties.begin();tel != properties.end();++tel){
617 if ((*tel)->propname == _propname){
618 return ((*tel)->GetValue());
619 }
620 }
621 return 0.0;
622 }
623
Get_path(string const & _propname) const624 Prop_path* Section_prop::Get_path(string const& _propname) const {
625 for(const_it tel = properties.begin();tel != properties.end();++tel){
626 if ((*tel)->propname == _propname){
627 Prop_path* val = dynamic_cast<Prop_path*>((*tel));
628 if (val) return val; else return NULL;
629 }
630 }
631 return NULL;
632 }
633
Get_multival(string const & _propname) const634 Prop_multival* Section_prop::Get_multival(string const& _propname) const {
635 for(const_it tel = properties.begin();tel != properties.end();++tel){
636 if ((*tel)->propname == _propname){
637 Prop_multival* val = dynamic_cast<Prop_multival*>((*tel));
638 if(val) return val; else return NULL;
639 }
640 }
641 return NULL;
642 }
643
Get_multivalremain(string const & _propname) const644 Prop_multival_remain* Section_prop::Get_multivalremain(string const& _propname) const {
645 for(const_it tel = properties.begin();tel != properties.end();++tel){
646 if ((*tel)->propname == _propname){
647 Prop_multival_remain* val = dynamic_cast<Prop_multival_remain*>((*tel));
648 if (val) return val; else return NULL;
649 }
650 }
651 return NULL;
652 }
Get_prop(int index)653 Property* Section_prop::Get_prop(int index){
654 for(it tel = properties.begin();tel != properties.end();++tel){
655 if (!index--) return (*tel);
656 }
657 return NULL;
658 }
659
Get_string(string const & _propname) const660 const char* Section_prop::Get_string(string const& _propname) const {
661 for(const_it tel = properties.begin();tel != properties.end();++tel){
662 if ((*tel)->propname == _propname){
663 return ((*tel)->GetValue());
664 }
665 }
666 return "";
667 }
Get_hex(string const & _propname) const668 Hex Section_prop::Get_hex(string const& _propname) const {
669 for(const_it tel = properties.begin();tel != properties.end();++tel){
670 if ((*tel)->propname == _propname){
671 return ((*tel)->GetValue());
672 }
673 }
674 return 0;
675 }
676
HandleInputline(string const & gegevens)677 bool Section_prop::HandleInputline(string const& gegevens){
678 string str1 = gegevens;
679 string::size_type loc = str1.find('=');
680 if (loc == string::npos) return false;
681 string name = str1.substr(0,loc);
682 string val = str1.substr(loc + 1);
683
684 /* Remove quotes around value */
685 trim(val);
686 string::size_type length = val.length();
687 if (length > 1 &&
688 ((val[0] == '\"' && val[length - 1] == '\"' ) ||
689 (val[0] == '\'' && val[length - 1] == '\''))
690 ) val = val.substr(1,length - 2);
691
692 /* trim the results incase there were spaces somewhere */
693 trim(name);
694 trim(val);
695 for (auto &p : properties) {
696
697 if (strcasecmp(p->propname.c_str(), name.c_str()) != 0)
698 continue;
699
700 if (p->IsDeprecated()) {
701 LOG_MSG("CONFIG: Deprecated option '%s'", name.c_str());
702 LOG_MSG("CONFIG: %s", p->GetHelp());
703 return false;
704 }
705
706 return p->SetValue(val);
707 }
708 LOG_MSG("CONFIG: Unknown option %s", name.c_str());
709 return false;
710 }
711
PrintData(FILE * outfile) const712 void Section_prop::PrintData(FILE* outfile) const {
713 /* Now print out the individual section entries */
714
715 // Determine maximum length of the props in this section
716 int len = 0;
717 for (const auto &tel : properties) {
718 const auto prop_length = check_cast<int>(tel->propname.length());
719 len = std::max<int>(len, prop_length);
720 }
721
722 for (const auto &tel : properties) {
723
724 if (tel->IsDeprecated())
725 continue;
726
727 fprintf(outfile, "%-*s = %s\n",
728 std::min<int>(40, len),
729 tel->propname.c_str(),
730 tel->GetValue().ToString().c_str());
731 }
732 }
733
GetPropValue(string const & _property) const734 string Section_prop::GetPropValue(string const& _property) const {
735 for(const_it tel = properties.begin();tel != properties.end();++tel){
736 if (!strcasecmp((*tel)->propname.c_str(),_property.c_str())){
737 return (*tel)->GetValue().ToString();
738 }
739 }
740 return NO_SUCH_PROPERTY;
741 }
742
HandleInputline(const std::string & line)743 bool Section_line::HandleInputline(const std::string &line)
744 {
745 if (!data.empty()) data += "\n"; //Add return to previous line in buffer
746 data += line;
747 return true;
748 }
749
PrintData(FILE * outfile) const750 void Section_line::PrintData(FILE *outfile) const
751 {
752 fprintf(outfile, "%s", data.c_str());
753 }
754
GetPropValue(const std::string &) const755 std::string Section_line::GetPropValue(const std::string &) const
756 {
757 return NO_SUCH_PROPERTY;
758 }
759
PrintConfig(const std::string & filename) const760 bool Config::PrintConfig(const std::string &filename) const
761 {
762 char temp[50];
763 char helpline[256];
764 FILE *outfile = fopen(filename.c_str(), "w+t");
765 if (outfile == NULL) return false;
766
767 /* Print start of configfile and add a return to improve readibility. */
768 fprintf(outfile, MSG_Get("CONFIGFILE_INTRO"), VERSION);
769 fprintf(outfile, "\n");
770
771 for (auto tel = sectionlist.cbegin(); tel != sectionlist.cend(); ++tel) {
772 /* Print out the Section header */
773 safe_strcpy(temp, (*tel)->GetName());
774 lowcase(temp);
775 fprintf(outfile,"[%s]\n",temp);
776
777 Section_prop *sec = dynamic_cast<Section_prop *>(*tel);
778 if (sec) {
779 Property *p;
780 int i = 0;
781 size_t maxwidth = 0;
782 while ((p = sec->Get_prop(i++))) {
783 maxwidth = std::max(maxwidth, p->propname.length());
784 }
785 i=0;
786 char prefix[80];
787 int intmaxwidth = std::min<int>(60, check_cast<int>(maxwidth));
788 safe_sprintf(prefix, "\n# %*s ", intmaxwidth, "");
789 while ((p = sec->Get_prop(i++))) {
790
791 if (p->IsDeprecated())
792 continue;
793
794 std::string help = p->GetHelp();
795 std::string::size_type pos = std::string::npos;
796 while ((pos = help.find('\n', pos+1)) != std::string::npos) {
797 help.replace(pos, 1, prefix);
798 }
799
800 fprintf(outfile, "# %*s: %s", intmaxwidth, p->propname.c_str(), help.c_str());
801
802 std::vector<Value> values = p->GetValues();
803 if (!values.empty()) {
804 fprintf(outfile, "%s%s:", prefix, MSG_Get("CONFIG_SUGGESTED_VALUES"));
805 std::vector<Value>::const_iterator it = values.begin();
806 while (it != values.end()) {
807 if((*it).ToString() != "%u") { //Hack hack hack. else we need to modify GetValues, but that one is const...
808 if (it != values.begin()) fputs(",", outfile);
809 fprintf(outfile, " %s", (*it).ToString().c_str());
810 }
811 ++it;
812 }
813 fprintf(outfile,".");
814 }
815 fprintf(outfile, "\n");
816 }
817 } else {
818 upcase(temp);
819 strcat(temp,"_CONFIGFILE_HELP");
820 const char * helpstr = MSG_Get(temp);
821 const char * linestart = helpstr;
822 char * helpwrite = helpline;
823 while (*helpstr && static_cast<size_t>(helpstr - linestart) < sizeof(helpline)) {
824 *helpwrite++ = *helpstr;
825 if (*helpstr == '\n') {
826 *helpwrite = 0;
827 fprintf(outfile, "# %s", helpline);
828 helpwrite = helpline;
829 linestart = ++helpstr;
830 } else {
831 ++helpstr;
832 }
833 }
834 }
835
836 fprintf(outfile,"\n");
837 (*tel)->PrintData(outfile);
838 fprintf(outfile,"\n"); /* Always an empty line between sections */
839 }
840 fclose(outfile);
841 return true;
842 }
843
AddEarlySectionProp(const char * name,SectionFunction func,bool changeable_at_runtime)844 Section_prop *Config::AddEarlySectionProp(const char *name,
845 SectionFunction func,
846 bool changeable_at_runtime)
847 {
848 Section_prop *s = new Section_prop(name);
849 s->AddEarlyInitFunction(func, changeable_at_runtime);
850 sectionlist.push_back(s);
851 return s;
852 }
853
AddSection_prop(const char * section_name,SectionFunction func,bool changeable_at_runtime)854 Section_prop *Config::AddSection_prop(const char *section_name,
855 SectionFunction func,
856 bool changeable_at_runtime)
857 {
858 assertm(std::regex_match(section_name, std::regex{"[a-zA-Z0-9]+"}),
859 "Only letters and digits are allowed in section name");
860 Section_prop *s = new Section_prop(section_name);
861 s->AddInitFunction(func, changeable_at_runtime);
862 sectionlist.push_back(s);
863 return s;
864 }
865
~Section_prop()866 Section_prop::~Section_prop()
867 {
868 //ExecuteDestroy should be here else the destroy functions use destroyed properties
869 ExecuteDestroy(true);
870 /* Delete properties themself (properties stores the pointer of a prop */
871 for(it prop = properties.begin(); prop != properties.end(); ++prop)
872 delete (*prop);
873 }
874
AddSection_line(const char * section_name,SectionFunction func)875 Section_line *Config::AddSection_line(const char *section_name, SectionFunction func)
876 {
877 assertm(std::regex_match(section_name, std::regex{"[a-zA-Z0-9]+"}),
878 "Only letters and digits are allowed in section name");
879 Section_line *blah = new Section_line(section_name);
880 blah->AddInitFunction(func);
881 sectionlist.push_back(blah);
882 return blah;
883 }
884
Init() const885 void Config::Init() const
886 {
887 for (const auto &sec : sectionlist)
888 sec->ExecuteEarlyInit();
889
890 for (const auto &sec : sectionlist)
891 sec->ExecuteInit();
892 }
893
AddEarlyInitFunction(SectionFunction func,bool changeable_at_runtime)894 void Section::AddEarlyInitFunction(SectionFunction func, bool changeable_at_runtime)
895 {
896 early_init_functions.emplace_back(func, changeable_at_runtime);
897 }
898
AddInitFunction(SectionFunction func,bool changeable_at_runtime)899 void Section::AddInitFunction(SectionFunction func, bool changeable_at_runtime)
900 {
901 initfunctions.emplace_back(func, changeable_at_runtime);
902 }
903
AddDestroyFunction(SectionFunction func,bool changeable_at_runtime)904 void Section::AddDestroyFunction(SectionFunction func, bool changeable_at_runtime)
905 {
906 destroyfunctions.emplace_front(func, changeable_at_runtime);
907 }
908
ExecuteEarlyInit(bool init_all)909 void Section::ExecuteEarlyInit(bool init_all)
910 {
911 for (const auto &fn : early_init_functions)
912 if (init_all || fn.changeable_at_runtime)
913 fn.function(this);
914 }
915
ExecuteInit(bool initall)916 void Section::ExecuteInit(bool initall)
917 {
918 for (const auto &fn : initfunctions)
919 if (initall || fn.changeable_at_runtime)
920 fn.function(this);
921 }
922
ExecuteDestroy(bool destroyall)923 void Section::ExecuteDestroy(bool destroyall) {
924 typedef std::deque<Function_wrapper>::iterator func_it;
925 for (func_it tel = destroyfunctions.begin(); tel != destroyfunctions.end(); ) {
926 if (destroyall || (*tel).changeable_at_runtime) {
927 (*tel).function(this);
928 tel = destroyfunctions.erase(tel); //Remove destroyfunction once used
929 } else
930 ++tel;
931 }
932 }
933
~Config()934 Config::~Config()
935 {
936 for (auto cnt = sectionlist.rbegin(); cnt != sectionlist.rend(); ++cnt)
937 delete (*cnt);
938 }
939
GetSection(const std::string & section_name) const940 Section *Config::GetSection(const std::string §ion_name) const
941 {
942 for (auto *el : sectionlist) {
943 if (!strcasecmp(el->GetName(), section_name.c_str()))
944 return el;
945 }
946 return nullptr;
947 }
948
GetSectionFromProperty(const char * prop) const949 Section *Config::GetSectionFromProperty(const char *prop) const
950 {
951 for (auto *el : sectionlist) {
952 if (el->GetPropValue(prop) != NO_SUCH_PROPERTY)
953 return el;
954 }
955 return nullptr;
956 }
957
OverwriteAutoexec(const std::string & conf,const std::string & line)958 void Config::OverwriteAutoexec(const std::string &conf, const std::string &line)
959 {
960 // If we're in a new config file, then record that filename and reset
961 // the section
962 if (overwritten_autoexec_conf != conf) {
963 overwritten_autoexec_conf = conf;
964 overwritten_autoexec_section.data.clear();
965 }
966 overwritten_autoexec_section.HandleInputline(line);
967 }
968
GetOverwrittenAutoexecConf() const969 const std::string &Config::GetOverwrittenAutoexecConf() const
970 {
971 return overwritten_autoexec_conf;
972 }
973
GetOverwrittenAutoexecSection() const974 const Section_line &Config::GetOverwrittenAutoexecSection() const
975 {
976 return overwritten_autoexec_section;
977 }
978
ParseConfigFile(const std::string & type,const std::string & configfilename)979 bool Config::ParseConfigFile(const std::string &type, const std::string &configfilename)
980 {
981 // static bool first_configfile = true;
982 ifstream in(configfilename);
983 if (!in)
984 return false;
985 configfiles.push_back(configfilename);
986
987 // Get directory from configfilename, used with relative paths.
988 current_config_dir = configfilename;
989 auto split_pos = current_config_dir.rfind(CROSS_FILESPLIT);
990 if (split_pos == std::string::npos)
991 split_pos = 0; // No directory then erase string
992 current_config_dir.erase(split_pos);
993
994 string gegevens;
995 Section *currentsection = nullptr;
996
997 while (getline(in, gegevens)) {
998 /* strip leading/trailing whitespace */
999 trim(gegevens);
1000 if (gegevens.empty())
1001 continue;
1002
1003 switch (gegevens[0]) {
1004 case '%':
1005 case '\0':
1006 case '#':
1007 case ' ':
1008 case '\n': continue; break;
1009 case '[': {
1010 const auto bracket_pos = gegevens.find(']');
1011 if (bracket_pos == string::npos)
1012 continue;
1013 gegevens.erase(bracket_pos);
1014 const auto section_name = gegevens.substr(1);
1015 if (const auto sec = GetSection(section_name); sec)
1016 currentsection = sec;
1017 } break;
1018 default:
1019 if (currentsection) {
1020 currentsection->HandleInputline(gegevens);
1021
1022 // If this is an autoexec section, the above takes care of the joining
1023 // while this handles the overwrriten mode.
1024 // We need to be prepared for either scenario to play out because we
1025 // won't know the users final preferance until the very last configuration
1026 // file is processed.
1027 if (std::string_view(currentsection->GetName()) == "autoexec") {
1028 OverwriteAutoexec(configfilename, gegevens);
1029 }
1030 }
1031 break;
1032 }
1033 }
1034 current_config_dir.clear();//So internal changes don't use the path information
1035
1036 LOG_INFO("CONFIG: Loaded %s conf file %s", type.c_str(), configfilename.c_str());
1037
1038 return true;
1039 }
1040
parse_environ(const char * const * envp)1041 parse_environ_result_t parse_environ(const char * const * envp) noexcept
1042 {
1043 assert(envp);
1044
1045 // Filter envirnment variables in following format:
1046 // DOSBOX_SECTIONNAME_PROPNAME=VALUE (prefix, section, and property
1047 // names are case-insensitive).
1048 std::list<std::tuple<std::string, std::string>> props_to_set;
1049 for (const char * const *str = envp; *str; str++) {
1050 const char *env_var = *str;
1051 if (strncasecmp(env_var, "DOSBOX_", 7) != 0)
1052 continue;
1053 const std::string rest = (env_var + 7);
1054 const auto section_delimiter = rest.find('_');
1055 if (section_delimiter == string::npos)
1056 continue;
1057 const auto section_name = rest.substr(0, section_delimiter);
1058 if (section_name.empty())
1059 continue;
1060 const auto prop_name_and_value = rest.substr(section_delimiter + 1);
1061 if (prop_name_and_value.empty() || !isalpha(prop_name_and_value[0]))
1062 continue;
1063 props_to_set.emplace_back(std::make_tuple(section_name,
1064 prop_name_and_value));
1065 }
1066
1067 return props_to_set;
1068 }
1069
ParseEnv()1070 void Config::ParseEnv()
1071 {
1072 #if defined(_MSC_VER) || (defined(__MINGW32__) && defined(__clang__))
1073 const char *const *envp = _environ;
1074 #else
1075 const char *const *envp = environ;
1076 #endif
1077 if (envp == nullptr)
1078 return;
1079
1080 for (const auto &set_prop_desc : parse_environ(envp)) {
1081 const auto section_name = std::get<0>(set_prop_desc);
1082 Section *sec = GetSection(section_name);
1083 if (!sec)
1084 continue;
1085 const auto prop_name_and_value = std::get<1>(set_prop_desc);
1086 sec->HandleInputline(prop_name_and_value);
1087 }
1088 }
1089
SetStartUp(void (* _function)(void))1090 void Config::SetStartUp(void (*_function)(void)) {
1091 _start_function=_function;
1092 }
1093
StartUp()1094 void Config::StartUp()
1095 {
1096 (*_start_function)();
1097 }
1098
GetStartupVerbosity() const1099 Verbosity Config::GetStartupVerbosity() const
1100 {
1101 const Section* s = GetSection("dosbox");
1102 assert(s);
1103 const std::string user_choice = s->GetPropValue("startup_verbosity");
1104
1105 if (user_choice == "high")
1106 return Verbosity::High;
1107 if (user_choice == "medium")
1108 return Verbosity::Medium;
1109 if (user_choice == "low")
1110 return Verbosity::Low;
1111 if (user_choice == "splash_only")
1112 return Verbosity::SplashOnly;
1113 if (user_choice == "quiet")
1114 return Verbosity::Quiet;
1115 if (user_choice == "auto")
1116 return (cmdline->HasDirectory() || cmdline->HasExecutableName())
1117 ? Verbosity::InstantLaunch
1118 : Verbosity::High;
1119
1120 LOG_WARNING("SETUP: Unknown verbosity mode '%s', defaulting to 'high'",
1121 user_choice.c_str());
1122 return Verbosity::High;
1123 }
1124
FindExist(char const * const name,bool remove)1125 bool CommandLine::FindExist(char const * const name,bool remove) {
1126 cmd_it it;
1127 if (!(FindEntry(name,it,false))) return false;
1128 if (remove) cmds.erase(it);
1129 return true;
1130 }
1131
FindInt(char const * const name,int & value,bool remove)1132 bool CommandLine::FindInt(char const * const name,int & value,bool remove) {
1133 cmd_it it,it_next;
1134 if (!(FindEntry(name,it,true))) return false;
1135 it_next=it;++it_next;
1136 value=atoi((*it_next).c_str());
1137 if (remove) cmds.erase(it,++it_next);
1138 return true;
1139 }
1140
FindString(char const * const name,std::string & value,bool remove)1141 bool CommandLine::FindString(char const * const name,std::string & value,bool remove) {
1142 cmd_it it,it_next;
1143 if (!(FindEntry(name,it,true))) return false;
1144 it_next=it;++it_next;
1145 value=*it_next;
1146 if (remove) cmds.erase(it,++it_next);
1147 return true;
1148 }
1149
FindCommand(unsigned int which,std::string & value)1150 bool CommandLine::FindCommand(unsigned int which,std::string & value) {
1151 if (which<1) return false;
1152 if (which>cmds.size()) return false;
1153 cmd_it it=cmds.begin();
1154 for (;which>1;which--) it++;
1155 value=(*it);
1156 return true;
1157 }
1158
1159 // Was a directory provided on the command line?
HasDirectory() const1160 bool CommandLine::HasDirectory() const
1161 {
1162 for (const auto& arg : cmds)
1163 if (open_directory(arg.c_str()))
1164 return true;
1165 return false;
1166 }
1167
1168 // Was an executable filename provided on the command line?
HasExecutableName() const1169 bool CommandLine::HasExecutableName() const
1170 {
1171 for (const auto& arg : cmds)
1172 if (is_executable_filename(arg))
1173 return true;
1174 return false;
1175 }
1176
FindEntry(char const * const name,cmd_it & it,bool neednext)1177 bool CommandLine::FindEntry(char const * const name,cmd_it & it,bool neednext) {
1178 for (it = cmds.begin(); it != cmds.end(); ++it) {
1179 if (!strcasecmp((*it).c_str(),name)) {
1180 cmd_it itnext=it;++itnext;
1181 if (neednext && (itnext==cmds.end())) return false;
1182 return true;
1183 }
1184 }
1185 return false;
1186 }
1187
FindStringBegin(char const * const begin,std::string & value,bool remove)1188 bool CommandLine::FindStringBegin(char const* const begin,std::string & value, bool remove) {
1189 size_t len = strlen(begin);
1190 for (cmd_it it = cmds.begin(); it != cmds.end();++it) {
1191 if (strncmp(begin,(*it).c_str(),len)==0) {
1192 value=((*it).c_str() + len);
1193 if (remove) cmds.erase(it);
1194 return true;
1195 }
1196 }
1197 return false;
1198 }
1199
FindStringRemain(char const * const name,std::string & value)1200 bool CommandLine::FindStringRemain(char const * const name,std::string & value) {
1201 cmd_it it;value.clear();
1202 if (!FindEntry(name,it)) return false;
1203 ++it;
1204 for (;it != cmds.end();++it) {
1205 value += " ";
1206 value += (*it);
1207 }
1208 return true;
1209 }
1210
1211 /* Only used for parsing command.com /C
1212 * Allowing /C dir and /Cdir
1213 * Restoring quotes back into the commands so command /C mount d "/tmp/a b" works as intended
1214 */
FindStringRemainBegin(char const * const name,std::string & value)1215 bool CommandLine::FindStringRemainBegin(char const * const name,std::string & value) {
1216 cmd_it it;value.clear();
1217 if (!FindEntry(name,it)) {
1218 size_t len = strlen(name);
1219 for (it = cmds.begin();it != cmds.end();++it) {
1220 if (strncasecmp(name,(*it).c_str(),len)==0) {
1221 std::string temp = ((*it).c_str() + len);
1222 //Restore quotes for correct parsing in later stages
1223 if(temp.find(' ') != std::string::npos)
1224 value = std::string("\"") + temp + std::string("\"");
1225 else
1226 value = temp;
1227 break;
1228 }
1229 }
1230 if (it == cmds.end()) return false;
1231 }
1232 ++it;
1233 for (;it != cmds.end();++it) {
1234 value += " ";
1235 std::string temp = (*it);
1236 if(temp.find(' ') != std::string::npos)
1237 value += std::string("\"") + temp + std::string("\"");
1238 else
1239 value += temp;
1240 }
1241 return true;
1242 }
1243
GetStringRemain(std::string & value)1244 bool CommandLine::GetStringRemain(std::string & value) {
1245 if (!cmds.size()) return false;
1246
1247 cmd_it it = cmds.begin();value = (*it++);
1248 for(;it != cmds.end();++it) {
1249 value += " ";
1250 value += (*it);
1251 }
1252 return true;
1253 }
1254
1255
GetCount(void)1256 unsigned int CommandLine::GetCount(void) {
1257 return (unsigned int)cmds.size();
1258 }
1259
FillVector(std::vector<std::string> & vector)1260 void CommandLine::FillVector(std::vector<std::string> & vector) {
1261 for(cmd_it it = cmds.begin(); it != cmds.end(); ++it) {
1262 vector.push_back((*it));
1263 }
1264 #ifdef WIN32
1265 // add back the \" if the parameter contained a space
1266 for(Bitu i = 0; i < vector.size(); i++) {
1267 if(vector[i].find(' ') != std::string::npos) {
1268 vector[i] = "\""+vector[i]+"\"";
1269 }
1270 }
1271 #endif
1272 }
1273
GetParameterFromList(const char * const params[],std::vector<std::string> & output)1274 int CommandLine::GetParameterFromList(const char* const params[], std::vector<std::string> & output) {
1275 // return values: 0 = P_NOMATCH, 1 = P_NOPARAMS
1276 // TODO return nomoreparams
1277 int retval = 1;
1278 output.clear();
1279 enum {
1280 P_START, P_FIRSTNOMATCH, P_FIRSTMATCH
1281 } parsestate = P_START;
1282 cmd_it it = cmds.begin();
1283 while(it != cmds.end()) {
1284 bool found = false;
1285 for (int i = 0; *params[i] != 0; ++i) {
1286 if (!strcasecmp((*it).c_str(),params[i])) {
1287 // found a parameter
1288 found = true;
1289 switch(parsestate) {
1290 case P_START:
1291 retval = i+2;
1292 parsestate = P_FIRSTMATCH;
1293 break;
1294 case P_FIRSTMATCH:
1295 case P_FIRSTNOMATCH:
1296 return retval;
1297 }
1298 }
1299 }
1300 if(!found)
1301 switch(parsestate) {
1302 case P_START:
1303 retval = 0; // no match
1304 parsestate = P_FIRSTNOMATCH;
1305 output.push_back(*it);
1306 break;
1307 case P_FIRSTMATCH:
1308 case P_FIRSTNOMATCH:
1309 output.push_back(*it);
1310 break;
1311 }
1312 cmd_it itold = it;
1313 ++it;
1314 cmds.erase(itold);
1315
1316 }
1317
1318 return retval;
1319 /*
1320 bool CommandLine::FindEntry(char const * const name,cmd_it & it,bool neednext) {
1321 for (it=cmds.begin();it!=cmds.end();it++) {
1322 if (!strcasecmp((*it).c_str(),name)) {
1323 cmd_it itnext=it;itnext++;
1324 if (neednext && (itnext==cmds.end())) return false;
1325 return true;
1326 }
1327 }
1328 return false;
1329 */
1330
1331
1332 /*
1333 cmd_it it=cmds.begin();value=(*it++);
1334 while(it != cmds.end()) {
1335 if(params.
1336
1337 it++;
1338 }
1339 */
1340 // find next parameter
1341 //return -1;
1342
1343 }
1344
CommandLine(int argc,char const * const argv[])1345 CommandLine::CommandLine(int argc, char const *const argv[])
1346 {
1347 if (argc>0) {
1348 file_name=argv[0];
1349 }
1350 int i=1;
1351 while (i<argc) {
1352 cmds.push_back(argv[i]);
1353 i++;
1354 }
1355 }
1356
Get_arglength()1357 Bit16u CommandLine::Get_arglength()
1358 {
1359 if (cmds.empty())
1360 return 0;
1361
1362 size_t total_length = 0;
1363 for (const auto &cmd : cmds)
1364 total_length += cmd.size() + 1;
1365
1366 if (total_length > UINT16_MAX) {
1367 LOG_MSG("SETUP: Command line length too long, truncating");
1368 total_length = UINT16_MAX;
1369 }
1370 return static_cast<uint16_t>(total_length);
1371 }
1372
CommandLine(const char * name,const char * cmdline)1373 CommandLine::CommandLine(const char *name, const char *cmdline)
1374 {
1375 if (name) file_name=name;
1376 /* Parse the cmds and put them in the list */
1377 bool inword,inquote;char c;
1378 inword=false;inquote=false;
1379 std::string str;
1380 const char * c_cmdline=cmdline;
1381 while ((c=*c_cmdline)!=0) {
1382 if (inquote) {
1383 if (c!='"') str+=c;
1384 else {
1385 inquote=false;
1386 cmds.push_back(str);
1387 str.erase();
1388 }
1389 } else if (inword) {
1390 if (c!=' ') str+=c;
1391 else {
1392 inword=false;
1393 cmds.push_back(str);
1394 str.erase();
1395 }
1396 }
1397 else if (c=='\"') { inquote=true;}
1398 else if (c!=' ') { str+=c;inword=true;}
1399 c_cmdline++;
1400 }
1401 if (inword || inquote) cmds.push_back(str);
1402 }
1403
Shift(unsigned int amount)1404 void CommandLine::Shift(unsigned int amount) {
1405 while(amount--) {
1406 file_name = cmds.size()?(*(cmds.begin())):"";
1407 if(cmds.size()) cmds.erase(cmds.begin());
1408 }
1409 }
1410
1411 // Parse the user's configuration files starting with the primary, then custom
1412 // -conf's, and finally the local dosbox.conf
SETUP_ParseConfigFiles(const std::string & config_path)1413 void SETUP_ParseConfigFiles(const std::string &config_path)
1414 {
1415 std::string config_file;
1416
1417 // First: parse the user's primary config file
1418 const bool wants_primary_conf = !control->cmdline->FindExist("-noprimaryconf", true);
1419 if (wants_primary_conf) {
1420 Cross::GetPlatformConfigName(config_file);
1421 const std::string config_combined = config_path + config_file;
1422 control->ParseConfigFile("primary", config_combined);
1423 }
1424
1425 // Second: parse the local 'dosbox.conf', if present
1426 const bool wants_local_conf = !control->cmdline->FindExist("-nolocalconf", true);
1427 if (wants_local_conf) {
1428 control->ParseConfigFile("local", "dosbox.conf");
1429 }
1430
1431 // Finally: layer on custom -conf <files>
1432 while (control->cmdline->FindString("-conf", config_file, true)) {
1433 if (!control->ParseConfigFile("custom", config_file)) {
1434 // try to load it from the user directory
1435 if (!control->ParseConfigFile("custom", config_path + config_file)) {
1436 LOG_MSG("CONFIG: Can't open custom conf file: %s",
1437 config_file.c_str());
1438 }
1439 }
1440 }
1441
1442 // Create a new primary if permitted and no other conf was loaded
1443 if (wants_primary_conf && !control->configfiles.size()) {
1444 std::string new_config_path = config_path;
1445 Cross::CreatePlatformConfigDir(new_config_path);
1446 Cross::GetPlatformConfigName(config_file);
1447 const std::string config_combined = new_config_path + config_file;
1448 if (control->PrintConfig(config_combined)) {
1449 LOG_MSG("CONFIG: Wrote new primary conf file '%s'", config_combined.c_str());
1450 control->ParseConfigFile("new primary", config_combined);
1451 } else {
1452 LOG_WARNING("CONFIG: Unable to write a new primary conf file '%s'",
1453 config_combined.c_str());
1454 }
1455 }
1456 }
1457