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