1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 //
6 // Any parts of this program derived from the xMule, lMule or eMule project,
7 // or contributed by third-party developers are copyrighted by their
8 // respective authors.
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 // (at your option) 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, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
23 //
24 
25 //
26 // This file is for functions common to all three apps (amule, amuled, amulegui),
27 // but preprocessor-dependent (using theApp, thePrefs), so it is compiled seperately for each app.
28 //
29 
30 
31 #include <wx/wx.h>
32 #include <wx/cmdline.h>			// Needed for wxCmdLineParser
33 #include <wx/snglinst.h>		// Needed for wxSingleInstanceChecker
34 #include <wx/textfile.h>		// Needed for wxTextFile
35 #include <wx/config.h>			// Do_not_auto_remove (win32)
36 #include <wx/fileconf.h>
37 
38 #include "amule.h"			// Interface declarations.
39 #include <common/Format.h>		// Needed for CFormat
40 #include "CFile.h"			// Needed for CFile
41 #include "ED2KLink.h"			// Needed for command line passing of links
42 #include "FileLock.h"			// Needed for CFileLock
43 #include "GuiEvents.h"			// Needed for Notify_*
44 #include "KnownFile.h"
45 #include "Logger.h"
46 #include "MagnetURI.h"			// Needed for CMagnetURI
47 #include "Preferences.h"
48 #include "ScopedPtr.h"
49 #include "MuleVersion.h"		// Needed for GetMuleVersion()
50 
51 #ifndef CLIENT_GUI
52 #include "DownloadQueue.h"
53 #endif
54 
CamuleAppCommon()55 CamuleAppCommon::CamuleAppCommon()
56 {
57 	m_singleInstance = NULL;
58 	ec_config = false;
59 	m_geometryEnabled = false;
60 	if (IsRemoteGui()) {
61 		m_appName		= wxT("aMuleGUI");
62 		m_configFile	= wxT("remote.conf");
63 		m_logFile		= wxT("remotelogfile");
64 	} else {
65 		m_configFile	= wxT("amule.conf");
66 		m_logFile		= wxT("logfile");
67 
68 		if (IsDaemon()) {
69 			m_appName	= wxT("aMuleD");
70 		} else {
71 			m_appName	= wxT("aMule");
72 		}
73 	}
74 }
75 
~CamuleAppCommon()76 CamuleAppCommon::~CamuleAppCommon()
77 {
78 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
79 	//#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
80 #else
81 	delete m_singleInstance;
82 #endif
83 }
84 
RefreshSingleInstanceChecker()85 void CamuleAppCommon::RefreshSingleInstanceChecker()
86 {
87 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
88 	//#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
89 #else
90 	delete m_singleInstance;
91 	m_singleInstance = new wxSingleInstanceChecker(wxT("muleLock"), thePrefs::GetConfigDir());
92 #endif
93 }
94 
AddLinksFromFile()95 void CamuleAppCommon::AddLinksFromFile()
96 {
97 	const wxString fullPath = thePrefs::GetConfigDir() + wxT("ED2KLinks");
98 	if (!wxFile::Exists(fullPath)) {
99 		return;
100 	}
101 
102 	// Attempt to lock the ED2KLinks file.
103 	CFileLock lock((const char*)unicode2char(fullPath));
104 
105 	wxTextFile file(fullPath);
106 	if ( file.Open() ) {
107 		for ( unsigned int i = 0; i < file.GetLineCount(); i++ ) {
108 			wxString line = file.GetLine( i ).Strip( wxString::both );
109 
110 			if ( !line.IsEmpty() ) {
111 				// Special case! used by a secondary running mule to raise this one.
112 				if (line == wxT("RAISE_DIALOG")) {
113 					Notify_ShowGUI();
114 					continue;
115 				}
116 				unsigned long category = 0;
117 				if (line.AfterLast(wxT(':')).ToULong(&category) == true) {
118 					line = line.BeforeLast(wxT(':'));
119 				} else { // If ToULong returns false the category still can have been changed!
120 						 // This is fixed in wx 2.9
121 					category = 0;
122 				}
123 				theApp->downloadqueue->AddLink(line, category);
124 			}
125 		}
126 
127 		file.Close();
128 	} else {
129 		AddLogLineNS(_("Failed to open ED2KLinks file."));
130 	}
131 
132 	// Delete the file.
133 	wxRemoveFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
134 }
135 
136 
137 // Returns a magnet ed2k URI
CreateMagnetLink(const CAbstractFile * f)138 wxString CamuleAppCommon::CreateMagnetLink(const CAbstractFile *f)
139 {
140 	CMagnetURI uri;
141 
142 	uri.AddField(wxT("dn"), f->GetFileName().Cleanup(false).GetPrintable());
143 	uri.AddField(wxT("xt"), wxString(wxT("urn:ed2k:")) + f->GetFileHash().Encode().Lower());
144 	uri.AddField(wxT("xt"), wxString(wxT("urn:ed2khash:")) + f->GetFileHash().Encode().Lower());
145 	uri.AddField(wxT("xl"), CFormat(wxT("%d")) % f->GetFileSize());
146 
147 	return uri.GetLink();
148 }
149 
150 
151 // Returns a ed2k file URL
CreateED2kLink(const CAbstractFile * f,bool add_source,bool use_hostname,bool add_cryptoptions,bool add_AICH)152 wxString CamuleAppCommon::CreateED2kLink(const CAbstractFile *f, bool add_source, bool use_hostname, bool add_cryptoptions, bool add_AICH)
153 {
154 	wxASSERT(!(!add_source && (use_hostname || add_cryptoptions)));
155 	// Construct URL like this: ed2k://|file|<filename>|<size>|<hash>|/
156 	wxString strURL = CFormat(wxT("ed2k://|file|%s|%i|%s|"))
157 		% f->GetFileName().Cleanup(false)
158 		% f->GetFileSize() % f->GetFileHash().Encode();
159 
160 	// Append the AICH info
161 	if (add_AICH) {
162 		const CKnownFile* kf = dynamic_cast<const CKnownFile*>(f);
163 		if (kf && kf->HasProperAICHHashSet()) {
164 			strURL << wxT("h=") << kf->GetAICHMasterHash() << wxT("|");
165 		}
166 	}
167 
168 	strURL << wxT("/");
169 
170 	if (add_source && theApp->IsConnected() && !theApp->IsFirewalled()) {
171 		// Create the first part of the URL
172 		strURL << wxT("|sources,");
173 		if (use_hostname) {
174 			strURL << thePrefs::GetYourHostname();
175 		} else {
176 			uint32 clientID = theApp->GetID();
177 			strURL = CFormat(wxT("%s%u.%u.%u.%u"))
178 				% strURL
179 				% (clientID & 0xff)
180 				% ((clientID >> 8) & 0xff)
181 				% ((clientID >> 16) & 0xff)
182 				% ((clientID >> 24) & 0xff);
183 		}
184 
185 		strURL << wxT(":") <<
186 			thePrefs::GetPort();
187 
188 		if (add_cryptoptions) {
189 			uint8 uSupportsCryptLayer = thePrefs::IsClientCryptLayerSupported() ? 1 : 0;
190 			uint8 uRequestsCryptLayer = thePrefs::IsClientCryptLayerRequested() ? 1 : 0;
191 			uint8 uRequiresCryptLayer = thePrefs::IsClientCryptLayerRequired() ? 1 : 0;
192 			uint16 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0) | (uSupportsCryptLayer ? 0x80 : 0x00);
193 
194 			strURL << wxT(":") << byCryptOptions;
195 
196 			if (byCryptOptions & 0x80) {
197 				strURL << wxT(":") << thePrefs::GetUserHash().Encode();
198 			}
199 		}
200 		strURL << wxT("|/");
201 	} else if (add_source) {
202 		AddLogLineC(_("WARNING: You can't add yourself as a source for an eD2k link while having a lowid."));
203 	}
204 
205 	// Result is "ed2k://|file|<filename>|<size>|<hash>|[h=<AICH master hash>|]/|sources,[(<ip>|<hostname>):<port>[:cryptoptions[:hash]]]|/"
206 	return strURL;
207 }
208 
209 
InitCommon(int argc,wxChar ** argv)210 bool CamuleAppCommon::InitCommon(int argc, wxChar ** argv)
211 {
212 	theApp->SetAppName(wxT("aMule"));
213 	FullMuleVersion = GetFullMuleVersion();
214 	OSDescription = wxGetOsDescription();
215 	OSType = OSDescription.BeforeFirst( wxT(' ') );
216 	if ( OSType.IsEmpty() ) {
217 		OSType = wxT("Unknown");
218 	}
219 
220 	// Parse cmdline arguments.
221 	wxCmdLineParser cmdline(argc, argv);
222 
223 	// Handle these arguments.
224 	cmdline.AddSwitch(wxT("v"), wxT("version"), wxT("Displays the current version number."));
225 	cmdline.AddSwitch(wxT("h"), wxT("help"), wxT("Displays this information."));
226 	cmdline.AddOption(wxT("c"), wxT("config-dir"), wxT("read config from <dir> instead of home"));
227 #ifdef AMULE_DAEMON
228 	cmdline.AddSwitch(wxT("f"), wxT("full-daemon"), wxT("Fork to background."));
229 	cmdline.AddOption(wxT("p"), wxT("pid-file"), wxT("After fork, create a pid-file in the given fullname file."));
230 	cmdline.AddSwitch(wxT("e"), wxT("ec-config"), wxT("Configure EC (External Connections)."));
231 #else
232 
233 #ifdef __WINDOWS__
234 	// MSW shows help otions in a dialog box, and the formatting doesn't fit there
235 #define HELPTAB wxT("\t")
236 #else
237 #define HELPTAB wxT("\t\t\t")
238 #endif
239 
240 	cmdline.AddOption(wxT("geometry"), wxEmptyString,
241 			wxT("Sets the geometry of the app.\n")
242 			HELPTAB wxT("<str> uses the same format as standard X11 apps:\n")
243 			HELPTAB wxT("[=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]"));
244 #endif // !AMULE_DAEMON
245 
246 	cmdline.AddSwitch(wxT("o"), wxT("log-stdout"), wxT("Print log messages to stdout."));
247 	cmdline.AddSwitch(wxT("r"), wxT("reset-config"), wxT("Resets config to default values."));
248 
249 #ifdef CLIENT_GUI
250 	cmdline.AddSwitch(wxT("s"), wxT("skip"), wxT("Skip connection dialog."));
251 #else
252 	// Change webserver path. This is also a config option, so this switch will go at some time.
253 	cmdline.AddOption(wxT("w"), wxT("use-amuleweb"), wxT("Specify location of amuleweb binary."));
254 #endif
255 #ifndef __WINDOWS__
256 	cmdline.AddSwitch(wxT("d"), wxT("disable-fatal"), wxT("Do not handle fatal exception."));
257 // Keep stdin open to run valgrind --gen_suppressions
258 	cmdline.AddSwitch(wxT("i"), wxT("enable-stdin"), wxT("Do not disable stdin."));
259 #endif
260 
261 	// Allow passing of links to the app
262 	cmdline.AddOption(wxT("t"), wxT("category"), wxT("Set category for passed ED2K links."), wxCMD_LINE_VAL_NUMBER);
263 	cmdline.AddParam(wxT("ED2K link"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE);
264 
265 	// wx asserts in debug mode if there is a check for an option that wasn't added.
266 	// So we have to wrap around the same #ifdefs as above. >:(
267 
268 	// Show help on --help or invalid commands
269 	if ( cmdline.Parse() ) {
270 		return false;
271 	} else if (cmdline.Found(wxT("help"))) {
272 		cmdline.Usage();
273 		return false;
274 	}
275 
276 	if ( cmdline.Found(wxT("version"))) {
277 		// This looks silly with logging macros that add a timestamp.
278 		printf("%s\n", (const char*)unicode2char(wxString(CFormat(wxT("%s (OS: %s)")) % FullMuleVersion % OSType)));
279 		return false;
280 	}
281 
282 	wxString configdir;
283 	if (cmdline.Found(wxT("config-dir"), &configdir)) {
284 		// Make an absolute path from the config dir
285 		wxFileName fn(configdir);
286 		fn.MakeAbsolute();
287 		configdir = fn.GetFullPath();
288 		if (configdir.Last() != wxFileName::GetPathSeparator()) {
289 			configdir += wxFileName::GetPathSeparator();
290 		}
291 		thePrefs::SetConfigDir(configdir);
292 	} else {
293 		thePrefs::SetConfigDir(/*OtherFunctions::*/GetConfigDir(m_configFile));
294 	}
295 
296 	// Backtracing works in MSW.
297 	// Problem is just that the backtraces are useless, because apparently the context gets lost
298 	// in the try/catch somewhere.
299 	// So leave it out.
300 #ifndef __WINDOWS__
301 	#if wxUSE_ON_FATAL_EXCEPTION
302 		if ( !cmdline.Found(wxT("disable-fatal")) ) {
303 			// catch fatal exceptions
304 			wxHandleFatalExceptions(true);
305 		}
306 	#endif
307 #endif
308 
309 	theLogger.SetEnabledStdoutLog(cmdline.Found(wxT("log-stdout")));
310 #ifdef AMULE_DAEMON
311 	enable_daemon_fork = cmdline.Found(wxT("full-daemon"));
312 	if ( cmdline.Found(wxT("pid-file"), &m_PidFile) ) {
313 		// Remove any existing PidFile
314 		if ( wxFileExists (m_PidFile) ) wxRemoveFile (m_PidFile);
315 	}
316 	ec_config = cmdline.Found(wxT("ec-config"));
317 #else
318 	enable_daemon_fork = false;
319 
320 	// Default geometry of the GUI. Can be changed with a cmdline argument...
321 	if ( cmdline.Found(wxT("geometry"), &m_geometryString) ) {
322 		m_geometryEnabled = true;
323 	}
324 #endif
325 
326 	if (theLogger.IsEnabledStdoutLog()) {
327 		if ( enable_daemon_fork ) {
328 			AddLogLineNS(wxT("Daemon will fork to background - log to stdout disabled"));	// localization not active yet
329 			theLogger.SetEnabledStdoutLog(false);
330 		} else {
331 			AddLogLineNS(wxT("Logging to stdout enabled"));
332 		}
333 	}
334 
335 	AddLogLineNS(wxT("Initialising ") + FullMuleVersion);
336 
337 	// Ensure that "~/.aMule/" is accessible.
338 	CPath outDir;
339 	if (!CheckMuleDirectory(wxT("configuration"), CPath(thePrefs::GetConfigDir()), wxEmptyString, outDir)) {
340 		return false;
341 	}
342 
343 	if (cmdline.Found(wxT("reset-config"))) {
344 		// Make a backup first.
345 		wxRemoveFile(thePrefs::GetConfigDir() + m_configFile + wxT(".backup"));
346 		wxRenameFile(thePrefs::GetConfigDir() + m_configFile, thePrefs::GetConfigDir() + m_configFile + wxT(".backup"));
347 		AddLogLineNS(CFormat(wxT("Your settings have been reset to default values.\nThe old config file has been saved as %s.backup\n")) % m_configFile);
348 	}
349 
350 	size_t linksPassed = cmdline.GetParamCount();	// number of links from the command line
351 	// cppcheck-suppress variableScope
352 	int linksActuallyPassed = 0;					// number of links that pass the syntax check
353 	if (linksPassed) {
354 		long cat = 0;
355 		if (!cmdline.Found(wxT("t"), &cat)) {
356 			cat = 0;
357 		}
358 
359 		wxTextFile ed2kFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
360 		if (!ed2kFile.Exists()) {
361 			ed2kFile.Create();
362 		}
363 		if (ed2kFile.Open()) {
364 			for (size_t i = 0; i < linksPassed; i++) {
365 				wxString link;
366 				if (CheckPassedLink(cmdline.GetParam(i), link, cat)) {
367 					ed2kFile.AddLine(link);
368 					linksActuallyPassed++;
369 				}
370 			}
371 			ed2kFile.Write();
372 		} else {
373 			AddLogLineCS(wxT("Failed to open 'ED2KLinks', cannot add links."));
374 		}
375 	}
376 
377 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
378 	//#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
379 	AddLogLineCS(wxT("WARNING: The check for other instances is currently disabled in amuled.\n"
380 		"Please make sure that no other instance of aMule is running or your files might be corrupted.\n"));
381 #else
382 	AddLogLineNS(wxT("Checking if there is an instance already running..."));
383 
384 	m_singleInstance = new wxSingleInstanceChecker();
385 	wxString lockfile = IsRemoteGui() ? wxT("muleLockRGUI") : wxT("muleLock");
386 	if (m_singleInstance->Create(lockfile, thePrefs::GetConfigDir())
387 		&& m_singleInstance->IsAnotherRunning()) {
388 		AddLogLineCS(CFormat(wxT("There is an instance of %s already running")) % m_appName);
389 		AddLogLineNS(CFormat(wxT("(lock file: %s%s)")) % thePrefs::GetConfigDir() % lockfile);
390 		if (linksPassed) {
391 			AddLogLineNS(CFormat(wxT("passed %d %s to it, finished")) % linksActuallyPassed
392 				% (linksPassed == 1 ? wxT("link") : wxT("links")));
393 			return false;
394 		}
395 
396 		// This is very tricky. The most secure way to communicate is via ED2K links file
397 		wxTextFile ed2kFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
398 		if (!ed2kFile.Exists()) {
399 			ed2kFile.Create();
400 		}
401 
402 		if (ed2kFile.Open()) {
403 			ed2kFile.AddLine(wxT("RAISE_DIALOG"));
404 			ed2kFile.Write();
405 
406 			AddLogLineNS(wxT("Raising current running instance."));
407 		} else {
408 			AddLogLineCS(wxT("Failed to open 'ED2KFile', cannot signal running instance."));
409 		}
410 
411 		return false;
412 	} else {
413 		AddLogLineNS(wxT("No other instances are running."));
414 	}
415 #endif
416 
417 #ifndef __WINDOWS__
418 	// Close standard-input
419 	if ( !cmdline.Found(wxT("enable-stdin")) )	{
420 		// The full daemon will close all std file-descriptors by itself,
421 		// so closing it here would lead to the closing on the first open
422 		// file, which is the logfile opened below
423 		if (!enable_daemon_fork) {
424 			close(0);
425 		}
426 	}
427 #endif
428 
429 	// Create the CFG file we shall use and set the config object as the global cfg file
430 	wxConfig::Set(new wxFileConfig( wxEmptyString, wxEmptyString, thePrefs::GetConfigDir() + m_configFile));
431 
432 	// Make a backup of the log file
433 	CPath logfileName = CPath(thePrefs::GetConfigDir() + m_logFile);
434 	if (logfileName.FileExists()) {
435 		CPath::BackupFile(logfileName, wxT(".bak"));
436 	}
437 
438 	// Open the log file
439 	if (!theLogger.OpenLogfile(logfileName.GetRaw())) {
440 		// use std err as last resolt to indicate problem
441 		fputs("ERROR: unable to open log file\n", stderr);
442 		// failure to open log is serious problem
443 		return false;
444 	}
445 
446 	// Load Preferences
447 	CPreferences::BuildItemList(thePrefs::GetConfigDir());
448 	CPreferences::LoadAllItems( wxConfigBase::Get() );
449 
450 #ifdef CLIENT_GUI
451 	m_skipConnectionDialog = cmdline.Found(wxT("skip"));
452 #else
453 	wxString amulewebPath;
454 	if (cmdline.Found(wxT("use-amuleweb"), &amulewebPath)) {
455 		thePrefs::SetWSPath(amulewebPath);
456 		AddLogLineNS(CFormat(wxT("Using amuleweb in '%s'.")) % amulewebPath);
457 	}
458 #endif
459 
460 	return true;
461 }
462 
463 /**
464  * Returns a description of the version of aMule being used.
465  *
466  * @return A detailed description of the aMule version, including application
467  *         name and wx information.
468  */
GetFullMuleVersion() const469 const wxString CamuleAppCommon::GetFullMuleVersion() const
470 {
471 	return GetMuleAppName() + wxT(" ") + GetMuleVersion();
472 }
473 
CheckPassedLink(const wxString & in,wxString & out,int cat)474 bool CamuleAppCommon::CheckPassedLink(const wxString &in, wxString &out, int cat)
475 {
476 	wxString link(in);
477 
478 	// restore ASCII-encoded pipes
479 	link.Replace(wxT("%7C"), wxT("|"));
480 	link.Replace(wxT("%7c"), wxT("|"));
481 
482 	if (link.compare(0, 7, wxT("magnet:")) == 0) {
483 		link = CMagnetED2KConverter(link);
484 		if (link.empty()) {
485 			AddLogLineCS(CFormat(wxT("Cannot convert magnet link to eD2k: %s")) % in);
486 			return false;
487 		}
488 	}
489 
490 	try {
491 		CScopedPtr<CED2KLink> uri(CED2KLink::CreateLinkFromUrl(link));
492 		out = uri.get()->GetLink();
493 		if (cat && uri.get()->GetKind() == CED2KLink::kFile) {
494 			out += CFormat(wxT(":%d")) % cat;
495 		}
496 		return true;
497 	} catch ( const wxString& err ) {
498 		AddLogLineCS(CFormat(wxT("Invalid eD2k link \"%s\" - ERROR: %s")) % link % err);
499 	}
500 	return false;
501 }
502 
503 
504 /**
505  * Checks permissions on a aMule directory, creating if needed.
506  *
507  * @param desc A description of the directory in question, used for error messages.
508  * @param directory The directory in question.
509  * @param alternative If the dir specified with 'directory' could not be created, try this instead.
510  * @param outDir Returns the used path.
511  * @return False on error.
512  */
CheckMuleDirectory(const wxString & desc,const CPath & directory,const wxString & alternative,CPath & outDir)513 bool CamuleAppCommon::CheckMuleDirectory(const wxString& desc, const CPath& directory, const wxString& alternative, CPath& outDir)
514 {
515 	wxString msg;
516 
517 	if (directory.IsDir(CPath::readwritable)) {
518 		outDir = directory;
519 		return true;
520 	} else if (directory.DirExists()) {
521 		// Strings are not translated here because translation isn't up yet.
522 		msg = CFormat(wxT("Permissions on the %s directory too strict!\n")
523 			wxT("aMule cannot proceed. To fix this, you must set read/write/exec\n")
524 			wxT("permissions for the folder '%s'"))
525 				% desc % directory;
526 	} else if (CPath::MakeDir(directory)) {
527 		outDir = directory;
528 		return true;
529 	} else {
530 		msg << CFormat(wxT("Could not create the %s directory at '%s'."))
531 			% desc % directory;
532 	}
533 
534 	// Attempt to use fallback directory.
535 	const CPath fallback(alternative);
536 	if (fallback.IsOk() && (directory != fallback)) {
537 		msg << wxT("\nAttempting to use default directory at location \n'")
538 			<< alternative << wxT("'.");
539 		if (theApp->ShowAlert(msg, wxT("Error accessing directory."), wxICON_ERROR | wxOK | wxCANCEL) == wxCANCEL) {
540 			outDir = CPath(wxEmptyString);
541 			return false;
542 		}
543 
544 		return CheckMuleDirectory(desc, fallback, wxEmptyString, outDir);
545 	}
546 
547 	theApp->ShowAlert(msg, wxT("Fatal error."), wxICON_ERROR | wxOK);
548 	outDir = CPath(wxEmptyString);
549 	return false;
550 }
551