1 /*
2 * The ManaPlus Client
3 * Copyright (C) 2004-2009 The Mana World Development Team
4 * Copyright (C) 2009-2010 The Mana Developers
5 * Copyright (C) 2011-2019 The ManaPlus Developers
6 * Copyright (C) 2019-2021 Andrei Karas
7 *
8 * This file is part of The ManaPlus Client.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "configuration.h"
25
26 #include "variabledata.h"
27
28 #include "fs/files.h"
29 #include "fs/paths.h"
30
31 #include "fs/virtfs/fs.h"
32
33 #include "listeners/configlistener.h"
34
35 #include "utils/cast.h"
36 #include "utils/checkutils.h"
37 #include "utils/foreach.h"
38 #ifdef DEBUG_CONFIG
39 #include "utils/stringmap.h"
40 #endif // DEBUG_CONFIG
41
42 #include "debug.h"
43
44 #ifdef DEBUG_CONFIG
45 StringIntMap optionsCount;
46 #define GETLOG() if (logger) {logger->log("config get: " + key); \
47 if (mIsMain) optionsCount[key] = 1; }
48 #else // DEBUG_CONFIG
49 #define GETLOG()
50 #endif // DEBUG_CONFIG
51
52 Configuration config; // XML file configuration reader
53 Configuration serverConfig; // XML file server configuration reader
54 Configuration features; // XML file features
55 Configuration branding; // XML branding information reader
56 Configuration paths; // XML default paths information reader
57
58 const std::string unusedKeys[] =
59 {
60 "BotCheckerWindowSticky",
61 "afkmessage",
62 "BotCheckerWindowVisible",
63 "BotCheckerWindowWinX",
64 "BotCheckerWindowWinY",
65 "hideShield",
66 "AttackRange",
67 "emoteshortcut0",
68 "screenshotDirectory2",
69 "AttackRangeBorder",
70 "AttackRangeBorderDelay",
71 "AttackRangeBorderGradient",
72 "AttackRangeDelay",
73 "AttackRangeGradient",
74 "Being",
75 "BeingDelay",
76 "BeingGradient",
77 "BeingPopupSkin",
78 "BotCheckerWindowSkin",
79 "BuySellSkin",
80 "BuySkin",
81 "ChatSkin",
82 "CollisionHighlight",
83 "CollisionHighlightDelay",
84 "CollisionHighlightGradient",
85 "ColorCross",
86 "ColorCrossDelay",
87 "ColorCrossGradient",
88 "ColorExperience",
89 "ColorExperienceGradient",
90 "ColorPickup",
91 "ColorPickupGradient",
92 "DebugSkin",
93 "DropShortcutSkin",
94 "EmoteShortcutSkin",
95 "EquipmentSkin",
96 "ExpInfo",
97 "ExpInfoDelay",
98 "ExpInfoGradient",
99 "Experience",
100 "ExperienceGradient",
101 "GM",
102 "GMDelay",
103 "GMGradient",
104 "Guild",
105 "GuildDelay",
106 "GuildGradient",
107 "GuildSkin",
108 "HelpSkin",
109 "Hit CriticalDelay",
110 "Hit CriticalGradient",
111 "Hit Monster Player",
112 "Hit Monster PlayerGradient",
113 "Hit Player Monster",
114 "Hit Player MonsterGradient",
115 "HitCriticalDelay",
116 "HitCriticalGradient",
117 "HitLocalPlayerCriticalDelay",
118 "HitLocalPlayerCriticalGradient",
119 "HitLocalPlayerMiss",
120 "HitLocalPlayerMissDelay",
121 "HitLocalPlayerMissGradient",
122 "HitLocalPlayerMonster",
123 "HitLocalPlayerMonsterDelay",
124 "HitLocalPlayerMonsterGradient",
125 "HitMonsterPlayer",
126 "HitMonsterPlayerDelay",
127 "HitMonsterPlayerGradient",
128 "HitPlayerMonster",
129 "HitPlayerMonsterDelay",
130 "HitPlayerMonsterGradient",
131 "HomePlace",
132 "HomePlaceBorder",
133 "HomePlaceBorderDelay",
134 "HomePlaceBorderGradient",
135 "HomePlaceDelay",
136 "HomePlaceGradient",
137 "InventorySkin",
138 "ItemPopupSkin",
139 "ItemShortcutSkin",
140 "Kill statsSkin",
141 "MiniStatusSkin",
142 "MinimapSkin",
143 "Miss",
144 "MissDelay",
145 "MissGradient",
146 "Monster",
147 "MonsterAttackRange",
148 "MonsterAttackRangeDelay",
149 "MonsterAttackRangeGradient",
150 "MonsterDelay",
151 "MonsterGradient",
152 "NPC",
153 "NPCDelay",
154 "NPCGradient",
155 "NpcTextSkin",
156 "OutfitsSkin",
157 "Particle",
158 "ParticleDelay",
159 "ParticleGradient",
160 "PartyDelay",
161 "PartyGradient",
162 "PartySkin",
163 "Personal ShopSkin",
164 "Pickup",
165 "PickupGradient",
166 "Player",
167 "PlayerDelay",
168 "PlayerGradient",
169 "PopupMenuSkin",
170 "PortalHighlight",
171 "PortalHighlightDelay",
172 "PortalHighlightGradient",
173 "RecorderSkin",
174 "RecorderWinX",
175 "RecorderWinY",
176 "RoadPoint",
177 "RoadPointDelay",
178 "RoadPointGradient",
179 "Self",
180 "SelfDelay",
181 "SelfGradient",
182 "SellSkin",
183 "ServerDialogSkin",
184 "ShopSkin",
185 "SkillsSkin",
186 "SocialCreatePopupSkin",
187 "SocialSkin",
188 "SpecialsSkin",
189 "SpeechSkin",
190 "SpellPopupSkin",
191 "SpellShortcutSkin",
192 "StatusPopupSkin",
193 "StatusSkin",
194 "StorageSkin",
195 "TextCommandEditorSkin",
196 "TextPopupSkin",
197 "TradeSkin",
198 "WhoIsOnlineSkin",
199 "emoteshortcut1",
200 "emoteshortcut2",
201 "emoteshortcut3",
202 "emoteshortcut4",
203 "emoteshortcut5",
204 "emoteshortcut6",
205 "emoteshortcut7",
206 "emoteshortcut8",
207 "emoteshortcut9",
208 "emoteshortcut10",
209 "emoteshortcut11",
210 "emoteshortcut12",
211 "emoteshortcut13",
212 "fastOpenGL",
213 "keyAutoCompleteChat",
214 "keyDeActivateChat",
215 "keyTargetClosest",
216 "keyWindowParty",
217 "mapalpha",
218 "port",
219 "shopBuyList",
220 "shopSellList",
221 "OutfitAwayIndex",
222 "playerHomes",
223 "remember",
224 "screenshotDirectory",
225 ""
226 };
227
setValue(const std::string & key,const std::string & value)228 void ConfigurationObject::setValue(const std::string &key,
229 const std::string &value)
230 {
231 mOptions[key] = value;
232 }
233
deleteKey(const std::string & key)234 void ConfigurationObject::deleteKey(const std::string &key)
235 {
236 mOptions.erase(key);
237 }
238
setValue(const std::string & key,const std::string & value)239 void Configuration::setValue(const std::string &key,
240 const std::string &value)
241 {
242 ConfigurationObject::setValue(key, value);
243 mUpdated = true;
244
245 // Notify listeners
246 const ListenerMapIterator list = mListenerMap.find(key);
247 if (list != mListenerMap.end())
248 {
249 Listeners listeners = list->second;
250 FOR_EACH (ListenerIterator, i, listeners)
251 (*i)->optionChanged(key);
252 }
253 }
254
incValue(const std::string & key)255 void Configuration::incValue(const std::string &key)
256 {
257 GETLOG();
258 const Options::const_iterator iter = mOptions.find(key);
259 setValue(key, (iter != mOptions.end())
260 ? atoi(iter->second.c_str()) + 1 : 1);
261 }
262
setSilent(const std::string & key,const std::string & value)263 void Configuration::setSilent(const std::string &key,
264 const std::string &value)
265 {
266 ConfigurationObject::setValue(key, value);
267 }
268
getValue(const std::string & key,const std::string & deflt) const269 std::string ConfigurationObject::getValue(const std::string &key,
270 const std::string &deflt) const
271 {
272 GETLOG();
273 const Options::const_iterator iter = mOptions.find(key);
274 return ((iter != mOptions.end()) ? iter->second : deflt);
275 }
276
getValue(const std::string & key,const int deflt) const277 int ConfigurationObject::getValue(const std::string &key,
278 const int deflt) const
279 {
280 GETLOG();
281 const Options::const_iterator iter = mOptions.find(key);
282 return (iter != mOptions.end()) ? atoi(iter->second.c_str()) : deflt;
283 }
284
getValueInt(const std::string & key,const int deflt) const285 int ConfigurationObject::getValueInt(const std::string &key,
286 const int deflt) const
287 {
288 GETLOG();
289 const Options::const_iterator iter = mOptions.find(key);
290 return (iter != mOptions.end()) ? atoi(iter->second.c_str()) : deflt;
291 }
292
getValueBool(const std::string & key,const bool deflt) const293 bool ConfigurationObject::getValueBool(const std::string &key,
294 const bool deflt) const
295 {
296 GETLOG();
297 const Options::const_iterator iter = mOptions.find(key);
298 if (iter != mOptions.end())
299 return atoi(iter->second.c_str()) != 0 ? true : false;
300 return deflt;
301 }
302
getValue(const std::string & key,const unsigned deflt) const303 unsigned ConfigurationObject::getValue(const std::string &key,
304 const unsigned deflt) const
305 {
306 GETLOG();
307 const Options::const_iterator iter = mOptions.find(key);
308 return (iter != mOptions.end()) ? CAST_U32(
309 atol(iter->second.c_str())) : deflt;
310 }
311
getValue(const std::string & key,const double deflt) const312 double ConfigurationObject::getValue(const std::string &key,
313 const double deflt) const
314 {
315 GETLOG();
316 const Options::const_iterator iter = mOptions.find(key);
317 return (iter != mOptions.end()) ? atof(iter->second.c_str()) : deflt;
318 }
319
deleteList(const std::string & name)320 void ConfigurationObject::deleteList(const std::string &name)
321 {
322 for (ConfigurationList::const_iterator
323 it = mContainerOptions[name].begin();
324 it != mContainerOptions[name].end(); ++it)
325 {
326 delete *it;
327 }
328
329 mContainerOptions[name].clear();
330 }
331
clear()332 void ConfigurationObject::clear()
333 {
334 for (std::map<std::string, ConfigurationList>::const_iterator
335 it = mContainerOptions.begin();
336 it != mContainerOptions.end(); ++it)
337 {
338 deleteList(it->first);
339 }
340 mOptions.clear();
341 mContainerOptions.clear();
342 }
343
ConfigurationObject()344 ConfigurationObject::ConfigurationObject() :
345 mOptions(),
346 #ifdef DEBUG_CONFIG
347 mContainerOptions(),
348 mLogKeys(false),
349 mIsMain(false)
350 #else // DEBUG_CONFIG
351
352 mContainerOptions()
353 #endif // DEBUG_CONFIG
354 {
355 }
356
~ConfigurationObject()357 ConfigurationObject::~ConfigurationObject()
358 {
359 clear();
360 }
361
Configuration()362 Configuration::Configuration() :
363 ConfigurationObject(),
364 mListenerMap(),
365 mConfigPath(),
366 mDefaultsData(),
367 mDirectory(),
368 mFilename(),
369 mUseResManager(UseVirtFs_false),
370 mUpdated(false)
371 {
372 #ifdef DEBUG_CONFIG
373 mLogKeys = false;
374 mIsMain = false;
375 #endif // DEBUG_CONFIG
376 }
377
cleanDefaults()378 void Configuration::cleanDefaults()
379 {
380 for (DefaultsData::const_iterator iter = mDefaultsData.begin();
381 iter != mDefaultsData.end();
382 ++iter)
383 {
384 delete iter->second;
385 }
386 mDefaultsData.clear();
387 }
388
~Configuration()389 Configuration::~Configuration()
390 {
391 cleanDefaults();
392 }
393
unload()394 void Configuration::unload()
395 {
396 cleanDefaults();
397 mConfigPath.clear();
398 mDirectory.clear();
399 mFilename.clear();
400 mUseResManager = UseVirtFs_false;
401 ConfigurationObject::clear();
402 }
403
getIntValue(const std::string & key) const404 int Configuration::getIntValue(const std::string &key) const
405 {
406 GETLOG();
407 int defaultValue = 0;
408 const Options::const_iterator iter = mOptions.find(key);
409 if (iter == mOptions.end())
410 {
411 const DefaultsData::const_iterator itdef
412 = mDefaultsData.find(key);
413
414 if (itdef != mDefaultsData.end() && (itdef->second != nullptr))
415 {
416 const VariableData *const data = itdef->second;
417 const VariableData::DataType type = static_cast<
418 VariableData::DataType>(data->getType());
419 if (type == VariableData::DATA_INT)
420 {
421 defaultValue = (static_cast<const IntData*>(
422 data))->getData();
423 }
424 else if (type == VariableData::DATA_STRING)
425 {
426 defaultValue = atoi((static_cast<const StringData*>(
427 data))->getData().c_str());
428 }
429 else if (type == VariableData::DATA_BOOL)
430 {
431 if ((static_cast<const BoolData*>(data))->getData())
432 defaultValue = 1;
433 else
434 defaultValue = 0;
435 }
436 else if (type == VariableData::DATA_FLOAT)
437 {
438 defaultValue = CAST_S32(
439 (static_cast<const FloatData*>(data))->getData());
440 }
441 }
442 else
443 {
444 reportAlways(
445 "%s: No integer value in registry for key %s",
446 mConfigPath.c_str(),
447 key.c_str())
448 }
449 }
450 else
451 {
452 defaultValue = atoi(iter->second.c_str());
453 }
454 return defaultValue;
455 }
456
resetIntValue(const std::string & key)457 int Configuration::resetIntValue(const std::string &key)
458 {
459 GETLOG();
460 int defaultValue = 0;
461 const DefaultsData::const_iterator itdef = mDefaultsData.find(key);
462 if (itdef == mDefaultsData.end())
463 {
464 reportAlways("%s: No integer value in registry for key %s",
465 mConfigPath.c_str(),
466 key.c_str())
467 }
468 else
469 {
470 const VariableData *const data = itdef->second;
471 if (data != nullptr &&
472 data->getType() == VariableData::DATA_INT)
473 {
474 defaultValue = (static_cast<const IntData*>(
475 data))->getData();
476 }
477 else
478 {
479 reportAlways("%s: No integer value in registry for key %s",
480 mConfigPath.c_str(),
481 key.c_str())
482 }
483 }
484 setValue(key, defaultValue);
485 return defaultValue;
486 }
487
getStringValue(const std::string & key) const488 std::string Configuration::getStringValue(const std::string &key) const
489 {
490 GETLOG();
491 std::string defaultValue;
492 const Options::const_iterator iter = mOptions.find(key);
493 if (iter == mOptions.end())
494 {
495 const DefaultsData::const_iterator
496 itdef = mDefaultsData.find(key);
497
498 if (itdef != mDefaultsData.end() &&
499 (itdef->second != nullptr))
500 {
501 const VariableData *const data = itdef->second;
502 const VariableData::DataType type = static_cast<
503 VariableData::DataType>(data->getType());
504 if (type == VariableData::DATA_STRING)
505 {
506 defaultValue = (static_cast<const StringData*>(
507 data))->getData();
508 }
509 else if (type == VariableData::DATA_BOOL)
510 {
511 if ((static_cast<const BoolData*>(data))->getData())
512 defaultValue = "1";
513 else
514 defaultValue = "0";
515 }
516 else if (type == VariableData::DATA_INT)
517 {
518 defaultValue = toString((static_cast<const IntData*>(
519 data))->getData());
520 }
521 else if (type == VariableData::DATA_FLOAT)
522 {
523 defaultValue = toString((static_cast<const FloatData*>(
524 data))->getData());
525 }
526 }
527 else
528 {
529 reportAlways("%s: No string value in registry for key %s",
530 mConfigPath.c_str(),
531 key.c_str())
532 }
533 }
534 else
535 {
536 defaultValue = iter->second;
537 }
538 return defaultValue;
539 }
540
541
getFloatValue(const std::string & key) const542 float Configuration::getFloatValue(const std::string &key) const
543 {
544 GETLOG();
545 float defaultValue = 0.0F;
546 const Options::const_iterator iter = mOptions.find(key);
547 if (iter == mOptions.end())
548 {
549 const DefaultsData::const_iterator itdef
550 = mDefaultsData.find(key);
551
552 if (itdef != mDefaultsData.end() &&
553 (itdef->second != nullptr))
554 {
555 const VariableData *const data = itdef->second;
556 const VariableData::DataType type = static_cast<
557 VariableData::DataType>(data->getType());
558 if (type == VariableData::DATA_FLOAT)
559 {
560 defaultValue = static_cast<float>(
561 (static_cast<const FloatData*>(data))->getData());
562 }
563 else if (type == VariableData::DATA_STRING)
564 {
565 defaultValue = static_cast<float>(atof((
566 static_cast<const StringData*>(
567 data))->getData().c_str()));
568 }
569 else if (type == VariableData::DATA_BOOL)
570 {
571 if ((static_cast<const BoolData*>(data))->getData())
572 defaultValue = 1;
573 else
574 defaultValue = 0;
575 }
576 else if (type == VariableData::DATA_INT)
577 {
578 defaultValue = static_cast<float>((
579 static_cast<const IntData*>(
580 data))->getData());
581 }
582 }
583 else
584 {
585 reportAlways("%s: No float value in registry for key %s",
586 mConfigPath.c_str(),
587 key.c_str())
588 }
589 }
590 else
591 {
592 defaultValue = static_cast<float>(atof(iter->second.c_str()));
593 }
594 return defaultValue;
595 }
596
getBoolValue(const std::string & key) const597 bool Configuration::getBoolValue(const std::string &key) const
598 {
599 GETLOG();
600 bool defaultValue = false;
601 const Options::const_iterator iter = mOptions.find(key);
602 if (iter == mOptions.end())
603 {
604 const DefaultsData::const_iterator itdef
605 = mDefaultsData.find(key);
606
607 if (itdef != mDefaultsData.end() &&
608 (itdef->second != nullptr))
609 {
610 const VariableData *const data = itdef->second;
611 const VariableData::DataType type = static_cast<
612 VariableData::DataType>(data->getType());
613 if (type == VariableData::DATA_BOOL)
614 {
615 defaultValue = (static_cast<const BoolData*>(
616 data))->getData();
617 }
618 else if (type == VariableData::DATA_INT)
619 {
620 if ((static_cast<const IntData*>(data))->getData() != 0)
621 defaultValue = true;
622 else
623 defaultValue = false;
624 }
625 else if (type == VariableData::DATA_STRING)
626 {
627 if ((static_cast<const StringData*>(
628 data))->getData() != "0")
629 {
630 defaultValue = true;
631 }
632 else
633 {
634 defaultValue = false;
635 }
636 }
637 if (type == VariableData::DATA_FLOAT)
638 {
639 if (CAST_S32((static_cast<const FloatData*>(
640 data))->getData()) != 0)
641 {
642 defaultValue = true;
643 }
644 else
645 {
646 defaultValue = false;
647 }
648 }
649 }
650 else
651 {
652 reportAlways(
653 "%s: No boolean value in registry for key %s",
654 mConfigPath.c_str(),
655 key.c_str())
656 }
657 }
658 else
659 {
660 defaultValue = getBoolFromString(iter->second);
661 }
662
663 return defaultValue;
664 }
665
resetBoolValue(const std::string & key)666 bool Configuration::resetBoolValue(const std::string &key)
667 {
668 GETLOG();
669 bool defaultValue = false;
670 const DefaultsData::const_iterator itdef = mDefaultsData.find(key);
671
672 if (itdef == mDefaultsData.end())
673 {
674 reportAlways("%s: No boolean value in registry for key %s",
675 mConfigPath.c_str(),
676 key.c_str())
677 }
678 else
679 {
680 const VariableData *const data = itdef->second;
681 if (data != nullptr &&
682 data->getType() == VariableData::DATA_BOOL)
683 {
684 defaultValue = (static_cast<const BoolData*>(data))->getData();
685 }
686 else
687 {
688 reportAlways("%s: No boolean value in registry for key %s",
689 mConfigPath.c_str(),
690 key.c_str())
691 }
692 }
693
694 setValue(key, defaultValue);
695 return defaultValue;
696 }
697
698
initFromXML(XmlNodeConstPtrConst parentNode)699 void ConfigurationObject::initFromXML(XmlNodeConstPtrConst parentNode)
700 {
701 clear();
702
703 if (parentNode == nullptr)
704 return;
705
706 for_each_xml_child_node(node, parentNode)
707 {
708 if (xmlNameEqual(node, "list"))
709 {
710 // list option handling
711 const std::string name = XML::getProperty(node,
712 "name", std::string());
713
714 for_each_xml_child_node(subnode, node)
715 {
716 if (xmlNameEqual(subnode, name.c_str()) &&
717 xmlTypeEqual(subnode, XML_ELEMENT_NODE))
718 {
719 ConfigurationObject *const cobj = new ConfigurationObject;
720 cobj->initFromXML(subnode); // recurse
721 mContainerOptions[name].push_back(cobj);
722 }
723 }
724 }
725 else if (xmlNameEqual(node, "option"))
726 {
727 // single option handling
728 const std::string name = XML::getProperty(node,
729 "name", std::string());
730 if (!name.empty())
731 {
732 mOptions[name] = XML::getProperty(node,
733 "value", std::string());
734 }
735 } // otherwise ignore
736 }
737 }
738
init(const std::string & filename,const UseVirtFs useResManager,const SkipError skipError)739 void Configuration::init(const std::string &filename,
740 const UseVirtFs useResManager,
741 const SkipError skipError)
742 {
743 cleanDefaults();
744 clear();
745 mFilename = filename;
746 mUseResManager = useResManager;
747
748 if (useResManager == UseVirtFs_true)
749 {
750 mConfigPath = "virtfs://" + filename;
751 mDirectory.clear();
752 if (VirtFs::exists(filename) == false)
753 {
754 logger->log("Warning: No configuration file (%s)",
755 filename.c_str());
756 return;
757 }
758 }
759 else
760 {
761 mConfigPath = filename;
762 logger->log1("init 1");
763 mDirectory = getRealPath(getFileDir(filename));
764 if (Files::existsLocal(filename) == false)
765 {
766 logger->log("Warning: No configuration file (%s)",
767 filename.c_str());
768 return;
769 }
770 }
771
772 XML::Document doc(filename,
773 useResManager,
774 skipError);
775 logger->log1("init 2");
776 if (doc.rootNode() == nullptr)
777 {
778 logger->log("Couldn't open configuration file: %s", filename.c_str());
779 return;
780 }
781
782 XmlNodeConstPtrConst rootNode = doc.rootNode();
783
784 if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "configuration"))
785 {
786 logger->log("Warning: No configuration file (%s)", filename.c_str());
787 return;
788 }
789
790 initFromXML(rootNode);
791 }
792
reInit()793 void Configuration::reInit()
794 {
795 XML::Document doc(mFilename, mUseResManager, SkipError_false);
796 if (doc.rootNode() == nullptr)
797 {
798 logger->log("Couldn't open configuration file: %s", mFilename.c_str());
799 return;
800 }
801
802 XmlNodeConstPtrConst rootNode = doc.rootNode();
803
804 if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "configuration"))
805 {
806 logger->log("Warning: No configuration file (%s)", mFilename.c_str());
807 return;
808 }
809
810 initFromXML(rootNode);
811 }
812
writeToXML(XmlTextWriterPtr writer)813 void ConfigurationObject::writeToXML(XmlTextWriterPtr writer)
814 {
815 FOR_EACH (Options::const_iterator, i, mOptions)
816 {
817 #ifdef DEBUG_CONFIG
818 if (mLogKeys)
819 {
820 if (optionsCount.find(i->first) == optionsCount.end())
821 logger->log("unused configuration option: " + i->first);
822 }
823 #endif // DEBUG_CONFIG
824
825 XmlTextWriterStartElement(writer, "option");
826 XmlTextWriterWriteAttribute(writer, "name", i->first.c_str());
827 XmlTextWriterWriteAttribute(writer, "value", i->second.c_str());
828 XmlTextWriterEndElement(writer);
829 }
830
831 for (std::map<std::string, ConfigurationList>::const_iterator
832 it = mContainerOptions.begin(), it_fend = mContainerOptions.end();
833 it != it_fend; ++ it)
834 {
835 const char *const name = it->first.c_str();
836
837 XmlTextWriterStartElement(writer, "list");
838 XmlTextWriterWriteAttribute(writer, "name", name);
839
840 // recurse on all elements
841 FOR_EACH (ConfigurationList::const_iterator, elt_it, it->second)
842 {
843 XmlTextWriterStartElement(writer, name);
844 if (*elt_it != nullptr)
845 (*elt_it)->writeToXML(writer);
846 XmlTextWriterEndElement(writer);
847 }
848
849 XmlTextWriterEndElement(writer);
850 }
851 }
852
writeUpdated()853 void Configuration::writeUpdated()
854 {
855 if (mUpdated)
856 write();
857 }
858
write()859 void Configuration::write()
860 {
861 BLOCK_START("Configuration::write")
862 if (mConfigPath.empty())
863 {
864 BLOCK_END("Configuration::write")
865 return;
866 }
867
868 mUpdated = false;
869 // Do not attempt to write to file that cannot be opened for writing
870 FILE *const testFile = fopen(mConfigPath.c_str(), "w");
871 if (testFile == nullptr)
872 {
873 reportAlways("Configuration::write() couldn't open %s for writing",
874 mConfigPath.c_str())
875 BLOCK_END("Configuration::write")
876 return;
877 }
878 fclose(testFile);
879
880 XmlTextWriterPtr writer = XmlNewTextWriterFilename(
881 mConfigPath.c_str(), 0);
882
883 if (writer == nullptr)
884 {
885 logger->log1("Configuration::write() error while creating writer");
886 BLOCK_END("Configuration::write")
887 return;
888 }
889
890 logger->log1("Configuration::write() writing configuration...");
891
892 XmlTextWriterSetIndent(writer, 1);
893 XmlTextWriterStartDocument(writer, nullptr, nullptr, nullptr);
894 // xmlTextWriterStartDocument(writer, nullptr, "utf8", nullptr);
895 XmlTextWriterStartRootElement(writer, "configuration");
896
897 writeToXML(writer);
898
899 XmlTextWriterEndDocument(writer);
900 XmlSaveTextWriterFilename(writer,
901 mConfigPath.c_str());
902 XmlFreeTextWriter(writer);
903 BLOCK_END("Configuration::write")
904 }
905
addListener(const std::string & key,ConfigListener * const listener)906 void Configuration::addListener(const std::string &key,
907 ConfigListener *const listener)
908 {
909 mListenerMap[key].push_front(listener);
910 }
911
removeListener(const std::string & key,ConfigListener * const listener)912 void Configuration::removeListener(const std::string &key,
913 ConfigListener *const listener)
914 {
915 mListenerMap[key].remove(listener);
916 }
917
918 #ifdef ENABLE_CHECKS
checkListeners(ConfigListener * const listener,const char * const file,const unsigned line)919 void Configuration::checkListeners(ConfigListener *const listener,
920 const char *const file,
921 const unsigned line)
922 {
923 FOR_EACH (ListenerMapIterator, it, mListenerMap)
924 {
925 Listeners listeners = it->second;
926 FOR_EACH (ListenerIterator, it2, listeners)
927 {
928 if (*it2 == listener)
929 {
930 logger->log("detected not cleaned listener: %p, %s:%u",
931 static_cast<void*>(listener), file, line);
932 exit(1);
933 }
934 }
935 }
936 }
937 #endif // ENABLE_CHECKS
938
removeListeners(ConfigListener * const listener)939 void Configuration::removeListeners(ConfigListener *const listener)
940 {
941 FOR_EACH (ListenerMapIterator, it, mListenerMap)
942 (it->second).remove(listener);
943 }
944
removeOldKeys()945 void Configuration::removeOldKeys()
946 {
947 if (mOptions.find(unusedKeys[0]) != mOptions.end() ||
948 mOptions.find(unusedKeys[1]) != mOptions.end() ||
949 mOptions.find(unusedKeys[2]) != mOptions.end())
950 {
951 int f = 0;
952 while (!unusedKeys[f].empty())
953 {
954 deleteKey(unusedKeys[f]);
955 logger->log("remove unused key: " + unusedKeys[f]);
956 f ++;
957 }
958 for (f = 0; f < 80; f ++)
959 {
960 const std::string str = toString(f);
961 deleteKey("Outfit" + str);
962 deleteKey("OutfitUnequip" + str);
963 deleteKey("commandShortcutCmd" + str);
964 deleteKey("commandShortcutFlags" + str);
965 deleteKey("commandShortcutSymbol" + str);
966 deleteKey("drop" + str);
967 deleteKey("shortcut" + str);
968 }
969 }
970 }
971