1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include <map>
26 #include "NstIoLog.hpp"
27 #include "NstIoStream.hpp"
28 #include "NstWindowUser.hpp"
29 #include "NstApplicationInstance.hpp"
30 #include "../core/NstXml.hpp"
31 
32 namespace Nestopia
33 {
34 	namespace Application
35 	{
36 		struct Configuration::Node
37 		{
38 			Node();
39 
40 			typedef Nes::Core::Xml Xml;
41 
42 			Node& Get(const HeapString&);
43 			const Node* Find(cstring) const;
44 
45 			void Load(Xml::Node);
46 			void Save(Xml::Node,wcstring=NULL) const;
47 			void CheckReference(wcstring) const;
48 
49 			typedef HeapString Key;
50 			typedef std::multimap<Key,Node> Map;
51 
52 			Map map;
53 			HeapString string;
54 
55 			struct
56 			{
57 				Map* map;
58 				Map::iterator item;
59 			}   parent;
60 
61 			mutable bool referenced;
62 		};
63 
Configuration()64 		Configuration::Configuration()
65 		: root(*new Node), save(false)
66 		{
67 			//const Path path( Instance::GetExePath(L"nestopia.xml") );
68 			const Path path( Instance::GetConfigPath(L"nestopia.xml") ); //bg
69 
70 			if (path.FileExists())
71 			{
72 				try
73 				{
74 					Nes::Core::Xml xml;
75 
76 					{
77 						Io::Stream::In file( path );
78 						xml.Read( file );
79 					}
80 
81 					if (!xml.GetRoot().IsType( L"configuration" ))
82 						throw 1;
83 
84 					if (HeapString(Instance::GetVersion()) != xml.GetRoot().GetAttribute(L"version").GetValue())
85 						Window::User::Warn( L"The configuration file is old and may not work reliably with this version of Nestopia!", L"Configuration file version conflict!" );
86 
87 					root.Load( xml.GetRoot().GetFirstChild() );
88 				}
89 				catch (...)
90 				{
91 					Reset( false );
92 					Window::User::Warn( L"Configuration file load error! Default settings will be used!" );
93 				}
94 			}
95 
96 			if (wcstring ptr = ::GetCommandLine())
97 			{
98 				for (uint quote=0; (*ptr > ' ') || (*ptr && quote); ++ptr)
99 				{
100 					if (*ptr == '\"')
101 						quote ^= 1;
102 				}
103 
104 				while (*ptr && *ptr <= ' ')
105 					++ptr;
106 
107 				if (*ptr)
108 				{
109 					if (*ptr != '-')
110 					{
111 						wcstring const offset = ptr;
112 
113 						for (uint quote=0; (*ptr > ' ') || (*ptr && quote); ++ptr)
114 						{
115 							if (*ptr == '\"')
116 								quote ^= 1;
117 						}
118 
119 						startupFile.Assign( offset, ptr - offset );
120 						startupFile.Remove( '\"' );
121 						startupFile.Trim();
122 
123 						// Win98/ME/2k fix
124 						if (startupFile.Length())
125 							startupFile = Instance::GetLongPath( startupFile.Ptr() );
126 					}
127 				}
128 			}
129 		}
130 
~Configuration()131 		Configuration::~Configuration()
132 		{
133 			if (save)
134 			{
135 				try
136 				{
137 					Nes::Core::Xml xml;
138 					xml.Create( L"configuration" ).AddAttribute( L"version", HeapString(Instance::GetVersion()).Ptr() );
139 					root.Save( xml.GetRoot() );
140 
141 					//Io::Stream::Out file( Instance::GetExePath(L"nestopia.xml") );
142 					Io::Stream::Out file( Instance::GetConfigPath(L"nestopia.xml") );//bg
143 					xml.Write( xml.GetRoot(), file );
144 				}
145 				catch (...)
146 				{
147 					Window::User::Warn( L"Couldn't save the configuration!" );
148 				}
149 			}
150 
151 			delete &root;
152 		}
153 
Reset(bool notify)154 		void Configuration::Reset(bool notify)
155 		{
156 			if (notify)
157 				root.CheckReference( L"" );
158 
159 			root.map.clear();
160 		}
161 
EnableSaving(bool enable)162 		void Configuration::EnableSaving(bool enable)
163 		{
164 			save = enable;
165 		}
166 
GetStartupFile() const167 		const Path& Configuration::GetStartupFile() const
168 		{
169 			return startupFile;
170 		}
171 
operator [](cstring key) const172 		Configuration::ConstSection Configuration::operator [] (cstring key) const
173 		{
174 			return root.Find( key );
175 		}
176 
operator [](cstring key)177 		Configuration::Section Configuration::operator [] (cstring key)
178 		{
179 			return &root.Get( key );
180 		}
181 
operator [](cstring key) const182 		Configuration::ConstSection Configuration::ConstSection::operator [] (cstring key) const
183 		{
184 			return node ? node->Find( key ) : NULL;
185 		}
186 
operator [](uint i) const187 		Configuration::ConstSection Configuration::ConstSection::operator [] (uint i) const
188 		{
189 			if (node)
190 			{
191 				if (i)
192 				{
193 					Node::Map::iterator it( node->parent.item );
194 
195 					while (++it != node->parent.map->end() && it->first == node->parent.item->first)
196 					{
197 						if (!--i)
198 						{
199 							it->second.referenced = true;
200 							return &it->second;
201 						}
202 					}
203 				}
204 				else
205 				{
206 					return *this;
207 				}
208 			}
209 
210 			return NULL;
211 		}
212 
operator [](cstring key)213 		Configuration::Section Configuration::Section::operator [] (cstring key)
214 		{
215 			return &node->Get( key );
216 		}
217 
operator [](uint i)218 		Configuration::Section Configuration::Section::operator [] (uint i)
219 		{
220 			if (i)
221 			{
222 				Node::Map::iterator it( node->parent.item );
223 
224 				while (++it != node->parent.map->end() && it->first == node->parent.item->first)
225 				{
226 					if (!--i)
227 						return &it->second;
228 				}
229 
230 				const Node::Map::value_type item( node->parent.item->first,Node() );
231 
232 				do
233 				{
234 					it = node->parent.map->insert( it, item );
235 					it->second.parent.map = node->parent.map;
236 					it->second.parent.item = it;
237 				}
238 				while (--i);
239 
240 				return &it->second;
241 			}
242 			else
243 			{
244 				return *this;
245 			}
246 		}
247 
Str()248 		HeapString& Configuration::Section::Str()
249 		{
250 			return node->string;
251 		}
252 
Str() const253 		GenericString Configuration::ConstSection::Str() const
254 		{
255 			return node ? GenericString(node->string) : GenericString();
256 		}
257 
Int() const258 		ulong Configuration::ConstSection::Int() const
259 		{
260 			ulong i;
261 			return node && (node->string >> i) ? i : 0;
262 		}
263 
Int(ulong d) const264 		ulong Configuration::ConstSection::Int(ulong d) const
265 		{
266 			ulong i;
267 			return node && (node->string >> i) ? i : d;
268 		}
269 
operator =(ulong i)270 		void Configuration::Section::IntProxy::operator = (ulong i)
271 		{
272 			node.string << i;
273 		}
274 
operator =(bool yes)275 		void Configuration::Section::YesNoProxy::operator = (bool yes)
276 		{
277 			node.string.Assign( yes ? L"yes" : L"no", yes ? 3 : 2 );
278 		}
279 
Yes() const280 		bool Configuration::ConstSection::Yes() const
281 		{
282 			return node && node->string == L"yes";
283 		}
284 
No() const285 		bool Configuration::ConstSection::No() const
286 		{
287 			return node && node->string == L"no";
288 		}
289 
Node()290 		Configuration::Node::Node()
291 		: referenced(false) {}
292 
Get(const HeapString & key)293 		Configuration::Node& Configuration::Node::Get(const HeapString& key)
294 		{
295 			Map::iterator it(map.lower_bound( key ));
296 
297 			if (it == map.end() || it->first != key)
298 			{
299 				it = map.insert( it, Map::value_type(key,Node()) );
300 				it->second.parent.map = &map;
301 				it->second.parent.item = it;
302 			}
303 
304 			return it->second;
305 		}
306 
Find(cstring key) const307 		const Configuration::Node* Configuration::Node::Find(cstring key) const
308 		{
309 			Map::const_iterator it(map.find( key ));
310 
311 			if (it != map.end())
312 			{
313 				it->second.referenced = true;
314 				return &it->second;
315 			}
316 
317 			return NULL;
318 		}
319 
Load(Xml::Node xmlNode)320 		void Configuration::Node::Load(Xml::Node xmlNode)
321 		{
322 			do
323 			{
324 				Map::iterator it(map.insert( Map::value_type(xmlNode.GetType(),Node()) ));
325 
326 				it->second.parent.map = &map;
327 				it->second.parent.item = it;
328 
329 				if (const Xml::Node xmlChild=xmlNode.GetFirstChild())
330 					it->second.Load( xmlChild );
331 				else
332 					it->second.string = xmlNode.GetValue();
333 
334 				xmlNode = xmlNode.GetNextSibling();
335 			}
336 			while (xmlNode);
337 		}
338 
Save(Xml::Node node,wcstring const key) const339 		void Configuration::Node::Save(Xml::Node node,wcstring const key) const
340 		{
341 			NST_ASSERT( node && (!key || *key) );
342 
343 			if (map.empty())
344 			{
345 				if (key)
346 					node.AddChild( key, string.Ptr() );
347 			}
348 			else
349 			{
350 				if (key)
351 					node = node.AddChild( key );
352 
353 				for (Map::const_iterator it(map.begin()), end(map.end()); it != end; ++it)
354 					it->second.Save( node, it->first.Ptr() );
355 			}
356 		}
357 
CheckReference(wcstring const key) const358 		void Configuration::Node::CheckReference(wcstring const key) const
359 		{
360 			NST_ASSERT( key );
361 
362 			if (map.empty())
363 			{
364 				if (*key && !referenced)
365 					Io::Log() << "Configuration: warning, unused/invalid parameter: \"" << key << "\"\r\n";
366 			}
367 			else for (Map::const_iterator it(map.begin()), end(map.end()); it != end; ++it)
368 			{
369 				it->second.CheckReference( it->first.Ptr() );
370 			}
371 		}
372 	}
373 }
374