main.cpp
1 //------------------------------------------------------------------------------
2 // main.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 
24 #include <QApplication>
25 #include <QDir>
26 #include <QFile>
27 #include <QHashIterator>
28 #include <QLabel>
29 #include <QMainWindow>
30 #include <QMessageBox>
31 #include <QMetaType>
32 #include <QObject>
33 #include <QSslConfiguration>
34 #include <QThreadPool>
35 #include <QTimer>
36 
37 #include <cstdio>
38 
39 #include "application.h"
40 #include "cmdargshelp.h"
41 #include "commandlinetokenizer.h"
42 #include "configuration/doomseekerconfig.h"
43 #include "configuration/passwordscfg.h"
44 #include "configuration/queryspeed.h"
45 #include "connectionhandler.h"
46 #include "datapaths.h"
47 #include "doomseekerfilepaths.h"
48 #include "fileutils.h"
49 #include "gui/createserverdialog.h"
50 #include "gui/mainwindow.h"
51 #include "gui/remoteconsole.h"
52 #include "ini/ini.h"
53 #include "ip2c/ip2c.h"
54 #include "irc/configuration/ircconfig.h"
55 #include "localization.h"
56 #include "log.h"
57 #include "main.h"
58 #include "pattern.h"
59 #include "plugins/engineplugin.h"
60 #include "plugins/pluginloader.h"
61 #include "refresher/refresher.h"
62 #include "serverapi/server.h"
63 #include "serverapi/server.h"
64 #include "strings.hpp"
65 #include "tests/testruns.h"
66 #include "updater/updateinstaller.h"
67 #include "versiondump.h"
68 #include "wadseeker/wadseeker.h"
69 
70 #ifdef Q_OS_OPENBSD
71 #include <unistd.h>
72 #endif
73 
75 
76 
77 Main::Main(int argc, char *argv[])
78  : arguments(argv), argumentsCount(argc),
79  startCreateGame(false), startRcon(false)
80 {
81  #ifdef Q_OS_OPENBSD
82  pledge ("stdio rpath wpath cpath tmppath inet mcast fattr chown flock unix "
83  "dns getpw sendfd recvfd tty proc exec prot_exec ps audio video unveil",
84  "");
85  #endif
86  bIsFirstRun = false;
87  bTestMode = false;
88  bPortableMode = false;
89  bVersionDump = false;
90  logVerbosity = LV_Default;
91  updateFailedCode = 0;
92 
93  qRegisterMetaType<Pattern>("Pattern");
94  qRegisterMetaType<ServerPtr>("ServerPtr");
95 
96 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
97  qRegisterMetaTypeStreamOperators<Pattern>("Pattern");
98  qRegisterMetaType<ServerCPtr>("ServerCPtr");
99 #endif
100 }
101 
102 Main::~Main()
103 {
104  if (Application::isInit())
105  {
106  gApp->stopRunning();
107  }
108 
109  if (Refresher::isInstantiated())
110  {
111  Refresher::instance()->quit();
112  Refresher::deinstantiate();
113  }
114 
115  // We can't save a config if we haven't initalized the program!
116  if (Application::isInit())
117  {
118  gConfig.saveToFile();
119  gConfig.dispose();
120 
121  gIRCConfig.saveToFile();
122  gIRCConfig.dispose();
123  }
124 
125  IP2C::deinstantiate();
126 
129 }
130 
131 int Main::connectToServerByURL()
132 {
133  ConnectionHandler *handler = ConnectionHandler::connectByUrl(connectUrl);
134 
135  if (handler)
136  {
137  connect(handler, SIGNAL(finished(int)), gApp, SLOT(quit()));
138  handler->run();
139  int ret = gApp->exec();
140  delete handler;
141  return ret;
142  }
143  return 0;
144 }
145 
146 // This method is an exception to sorting everything in alphabetical order
147 // because it's... the main method.
149 {
150  if (!interpretCommandLineParameters())
151  {
152  return 0;
153  }
154  applyLogVerbosity();
155 
156  Application::init(argumentsCount, arguments);
157  #ifdef Q_OS_DARWIN
158  // In Mac OS X it is abnormal to have menu icons unless it's a shortcut to a file of some kind.
159  gApp->setAttribute(Qt::AA_DontShowIconsInMenus);
160  #endif
161 
162  gLog << "Starting Doomseeker. Hello World! :)";
163  gLog << "Setting up data directories.";
164 
165  if (!initDataDirectories())
166  return 0;
167 
168  initCaCerts();
169 
170  PluginLoader::init(gDefaultDataPaths->pluginSearchLocationPaths(),
171  pluginsEnabled);
172  if (bVersionDump)
173  {
174  return runVersionDump();
175  }
176  PluginUrlHandler::registerAll();
177 
178  if (bTestMode)
179  {
180  return runTestMode();
181  }
182 
183  initMainConfig();
184  #ifdef WITH_AUTOUPDATES
185  // Handle pending update installations.
186  UpdateInstaller::ErrorCode updateInstallerResult
187  = (UpdateInstaller::ErrorCode)installPendingUpdates();
188  if (updateInstallerResult == UpdateInstaller::EC_Ok)
189  {
190  return 0;
191  }
192  #endif
193 
194  initLocalizationsDefinitions();
195  initIP2C();
196  initPasswordsConfig();
197  initPluginConfig();
198  initIRCConfig();
199 
200  if (startCreateGame)
201  {
202  QTimer::singleShot(0, this, SLOT(runCreateGame()));
203  }
204  else if (startRcon)
205  {
206  QTimer::singleShot(0, this, SLOT(runRemoteConsole()));
207  }
208  else if (connectUrl.isValid())
209  {
210  setupRefreshingThread();
211  return connectToServerByURL();
212  }
213  else
214  {
215  setupRefreshingThread();
216  createMainWindow();
217  #ifdef WITH_AUTOUPDATES
218  // Handle auto update: display update failure or start auto update
219  // check/download.
220  if (updateFailedCode != 0)
221  {
222  // This is when updater program failed to install the update.
223  gApp->mainWindow()->setDisplayUpdaterProcessFailure(updateFailedCode);
224  }
225  else if (updateInstallerResult != UpdateInstaller::EC_NothingToUpdate)
226  {
227  // This is when Doomseeker failed to start the updater program.
228  gApp->mainWindow()->setDisplayUpdateInstallerError(updateInstallerResult);
229  }
230  else
231  {
232  if (gConfig.autoUpdates.updateMode != DoomseekerConfig::AutoUpdates::UM_Disabled)
233  {
234  QTimer::singleShot(0, gApp->mainWindow(), SLOT(checkForUpdatesAuto()));
235  }
236  }
237  #endif
238  }
239 
240  gLog << tr("Init finished.");
241  gLog.addUnformattedEntry("================================\n");
242 
243  int returnCode = gApp->exec();
244 
245  #ifdef WITH_AUTOUPDATES
247  {
248  // Code must be reset because the install method
249  // doesn't do the actual installation if it's not equal to zero.
250  updateFailedCode = 0;
251  int installResult = installPendingUpdates();
252  if (installResult != UpdateInstaller::EC_Ok
253  && installResult != UpdateInstaller::EC_NothingToUpdate)
254  {
255  QMessageBox::critical(nullptr, tr("Doomseeker - Updates Install Failure"),
256  UpdateInstaller::errorCodeToStr((UpdateInstaller::ErrorCode)installResult));
257  }
258  }
259  #endif
260 
261  return returnCode;
262 }
263 
264 int Main::runTestMode()
265 {
266  // Setup
267  gLog << "Entering test mode.";
268  gLog << "";
269  TestCore testCore;
270 
271  // Call tests here.
272  TestRuns::pTestCore = &testCore;
273  TestRuns::callTests();
274 
275  // Summary
276  QString strSucceded = "Tests succeeded: %1";
277  QString strFailed = "Tests failed: %1";
278  QString strPercentage = "Pass percentage: %1%";
279 
280  float passPercentage = (float)testCore.numTestsSucceeded() / (float)testCore.numTests();
281  passPercentage *= 100.0f;
282 
283  gLog << "==== TESTS SUMMARY: ====";
284  gLog << strSucceded.arg(testCore.numTestsSucceeded(), 6);
285  gLog << strFailed.arg(testCore.numTestsFailed(), 6);
286  gLog << strPercentage.arg(passPercentage, 6, 'f', 2);
287  gLog << "==== Done. ====";
288 
289  return testCore.numTestsFailed();
290 }
291 
292 int Main::runVersionDump()
293 {
294  QFile outfile;
295  QString error;
296  if (!versionDumpFile.isEmpty())
297  {
298  outfile.setFileName(versionDumpFile);
299  if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
300  {
301  error = tr("Failed to open file '%1'.").arg(versionDumpFile);
302  }
303  }
304  else
305  {
306  // Use stdout instead.
307  if (!outfile.open(stdout, QIODevice::WriteOnly))
308  {
309  error = tr("Failed to open stdout.");
310  }
311  }
312  if (!error.isEmpty())
313  {
314  gLog.setPrintingToStderr(true);
315  gLog << error;
316  return 2;
317  }
318 
319  gLog << tr("Dumping version info to file in JSON format.");
320  VersionDump::dumpJsonToIO(outfile);
321  return 0;
322 }
323 
324 void Main::applyLogVerbosity()
325 {
326  gLog.setPrintingToStderr(shouldLogToStderr());
327 }
328 
329 void Main::createMainWindow()
330 {
331  gLog << tr("Preparing GUI.");
332 
333  gApp->setMainWindow(new MainWindow(gApp));
334  gApp->mainWindow()->show();
335 
336  if (bIsFirstRun)
337  {
338  gApp->mainWindow()->notifyFirstRun();
339  }
340 }
341 
342 void Main::runCreateGame()
343 {
344  gLog << tr("Starting Create Game box.");
345  auto dialog = new CreateServerDialog(GameCreateParams::Host, nullptr);
346  dialog->setWindowIcon(Application::icon());
347  dialog->show();
348 }
349 
350 void Main::runRemoteConsole()
351 {
352  gLog << tr("Starting RCon client.");
353  if (rconPluginName.isEmpty())
354  {
355  bool canAnyEngineRcon = false;
356  for (unsigned int i = 0; i < gPlugins->numPlugins(); i++)
357  {
358  const EnginePlugin *info = gPlugins->plugin(i)->info();
359  if (info->server(QHostAddress("localhost"), 0)->hasRcon())
360  {
361  canAnyEngineRcon = true;
362  break;
363  }
364  }
365  if (!canAnyEngineRcon)
366  {
367  QString error = tr("None of the currently loaded game plugins supports RCon.");
368  gLog << error;
369  QMessageBox::critical(nullptr, tr("Doomseeker RCon"), error);
370  gApp->exit(2);
371  return;
372  }
373 
374  auto rc = new RemoteConsole();
375  rc->show();
376  }
377  else
378  {
379  // Find plugin
380  int pIndex = gPlugins->pluginIndexFromName(rconPluginName);
381  if (pIndex == -1)
382  {
383  gLog << tr("Couldn't find the specified plugin: ") + rconPluginName;
384  gApp->exit(2);
385  return;
386  }
387 
388  // Check for RCon Availability.
389  const EnginePlugin *plugin = gPlugins->plugin(pIndex)->info();
390  ServerPtr server = plugin->server(QHostAddress(rconAddress), rconPort);
391  if (!server->hasRcon())
392  {
393  gLog << tr("Plugin does not support RCon.");
394  gApp->exit(2);
395  return;
396  }
397 
398  // Start it!
399  RemoteConsole *rc = new RemoteConsole(server);
400  rc->show();
401  }
402 }
403 
404 void Main::initCaCerts()
405 {
406  QString certsFilePath = DoomseekerFilePaths::cacerts();
407  QFile certsFile(certsFilePath);
408  if (!certsFilePath.isEmpty() && certsFile.exists())
409  {
410  gLog << tr("Loading extra CA certificates from '%1'.").arg(certsFilePath);
411  certsFile.open(QIODevice::ReadOnly);
412  QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
413  QList<QSslCertificate> cacerts = sslConf.caCertificates();
414  QList<QSslCertificate> extraCerts = QSslCertificate::fromDevice(&certsFile);
415  gLog << tr("Appending %n extra CA certificate(s).", nullptr, extraCerts.size());
416  cacerts.append(extraCerts);
417  sslConf.setCaCertificates(cacerts);
418  QSslConfiguration::setDefaultConfiguration(sslConf);
419  certsFile.close();
420  }
421 }
422 
423 bool Main::initDataDirectories()
424 {
425  if (bPortableMode)
426  {
427  gLog << tr("Running in the portable mode. Forcing current directory to: %1")
428  .arg(QCoreApplication::applicationDirPath());
429  if (!QDir::setCurrent(QCoreApplication::applicationDirPath()))
430  {
431  gLog << tr("Forcing the current directory failed! Path detection may misbehave.");
432  }
433  }
434 
435  DataPaths::initDefault(bPortableMode);
436  if (!baseDir.isEmpty())
437  gDefaultDataPaths->setBaseDir(baseDir);
438 
439  gLog << QString("Cache directory: %1").arg(gDefaultDataPaths->cacheLocationPath());
440  gLog << QString("Config directory: %1").arg(gDefaultDataPaths->programsDataDirectoryPath());
441  gLog << QString("Data directory: %1").arg(gDefaultDataPaths->localDataLocationPath());
442 
443  DoomseekerFilePaths::pDataPaths = gDefaultDataPaths;
444  QList<DirErrno> failedDirsErrno = gDefaultDataPaths->createDirectories();
445  if (!failedDirsErrno.isEmpty())
446  {
447  // Inform the user which directories failed and QUIT.
448  // We give an accurate error message of what is going wrong, thanks to errno.
449  QString errorMessage = tr("Doomseeker will not run because some directories cannot be used properly.\n");
450  for (const DirErrno &failedDirErrno : failedDirsErrno)
451  {
452  errorMessage += "\n[" + QString::number(failedDirErrno.errnoNum) + "] ";
453  errorMessage += failedDirErrno.directory.absolutePath() + ": ";
454  errorMessage += failedDirErrno.errnoString;
455  }
456  // Prompt the errorMessage and exit.
457  QMessageBox::critical(nullptr, tr("Doomseeker startup error"), errorMessage);
458  return false;
459  }
460 
461  // I think this directory should take priority, if user, for example,
462  // wants to update the ip2country file.
463  dataDirectories << gDefaultDataPaths->localDataLocationPath();
464  dataDirectories << QCoreApplication::applicationDirPath();
465 
466  // Continue with standard dirs:
467  dataDirectories << "./";
468  #if defined(Q_OS_LINUX)
469  // check in /usr/local/share/doomseeker/ on Linux
470  dataDirectories << INSTALL_PREFIX "/share/doomseeker/";
471  #endif
472 
473  dataDirectories << ":/";
474  QDir::setSearchPaths("data", dataDirectories);
475 
476  return true;
477 }
478 
479 int Main::initIP2C()
480 {
481  gLog << tr("Initializing IP2C database.");
482  IP2C::instance();
483 
484  return 0;
485 }
486 
487 void Main::initIRCConfig()
488 {
489  gLog << tr("Initializing IRC configuration file.");
490 
491  // This macro initializes the Singleton.
492  gIRCConfig;
493 
494  // Now try to access the configuration stored on drive.
495  QString configPath = DoomseekerFilePaths::ircIni();
496  if (!configPath.isEmpty())
497  {
498  if (gIRCConfig.setIniFile(configPath))
499  {
500  gIRCConfig.readFromFile();
501  }
502  }
503 }
504 
505 void Main::initLocalizationsDefinitions()
506 {
507  gLog << tr("Loading translations definitions");
508  Localization::get()->loadLocalizationsList(
509  DataPaths::staticDataSearchDirs(DataPaths::TRANSLATIONS_DIR_NAME));
510 
511  LocalizationInfo bestMatchedLocalization = Localization::get()->coerceBestMatchingLocalization(
512  gConfig.doomseeker.localization);
513  if (bestMatchedLocalization.isValid() && bestMatchedLocalization != LocalizationInfo::PROGRAM_NATIVE)
514  {
515  gLog << tr("Loading translation \"%1\".").arg(bestMatchedLocalization.localeName);
516  bool bSuccess = Localization::get()->loadTranslation(bestMatchedLocalization.localeName);
517  if (bSuccess)
518  {
519  gLog << tr("Translation loaded.");
520  }
521  else
522  {
523  gLog << tr("Failed to load translation.");
524  }
525  }
526 }
527 
528 void Main::initMainConfig()
529 {
530  gLog << tr("Initializing configuration file.");
531 
532  // This macro initializes the Singleton.
533  gConfig;
534 
535  // Now try to access the configuration stored on drive.
536  QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
537  if (configDirPath.isEmpty())
538  {
539  gLog << tr("Could not get an access to the settings directory. Configuration will not be saved.");
540  return;
541  }
542 
543  QString filePath = DoomseekerFilePaths::ini();
544 
545  // Check for first run.
546  QFileInfo iniFileInfo(filePath);
547  bIsFirstRun = !iniFileInfo.exists();
548 
549  // Init the config.
550  if (gConfig.setIniFile(filePath))
551  {
552  gConfig.readFromFile();
553  }
554 }
555 
556 void Main::initPasswordsConfig()
557 {
558  gLog << tr("Initializing passwords configuration file.");
559  // Now try to access the configuration stored on drive.
560  QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
561  if (configDirPath.isEmpty())
562  {
563  return;
564  }
565  QString filePath = DoomseekerFilePaths::passwordIni();
566  PasswordsCfg::initIni(filePath);
567 }
568 
569 void Main::initPluginConfig()
570 {
571  gLog << tr("Initializing configuration for plugins.");
572  gPlugins->initConfig();
573 }
574 
575 int Main::installPendingUpdates()
576 {
578  if (gConfig.autoUpdates.bPerformUpdateOnNextRun)
579  {
580  gConfig.autoUpdates.bPerformUpdateOnNextRun = false;
581  gConfig.saveToFile();
582  // Update should only be attempted if program was not called
583  // with "--update-failed" arg (previous update didn't fail).
584  if (updateFailedCode == 0)
585  {
586  UpdateInstaller updateInstaller;
587  updateInstallerResult = updateInstaller.startInstallation();
588  }
589  }
590  return updateInstallerResult;
591 }
592 
593 bool Main::interpretCommandLineParameters()
594 {
595  QString failure;
596  //first argument is the command to run the program, example: ./doomseeker. better use 1 instead of 0
597  for (int i = 1; i < argumentsCount && failure.isEmpty(); ++i)
598  {
599  const char *arg = arguments[i];
600 
601  if (strcmp(arg, "--basedir") == 0)
602  {
603  if (i + 1 < argumentsCount)
604  {
605  ++i;
606  baseDir = arguments[i];
607  }
608  else
609  {
610  failure = CmdArgsHelp::missingArgs(1, arg);
611  }
612  }
613  else if (strcmp(arg, "--connect") == 0)
614  {
615  if (i + 1 < argumentsCount)
616  {
617  ++i;
618  connectUrl = QUrl(arguments[i]);
619  }
620  else
621  {
622  //basically prevent the program from running if there are no arguments given.
623  failure = CmdArgsHelp::missingArgs(1, arg);
624  }
625  }
626  else if (strcmp(arg, "--create-game") == 0)
627  {
628  startCreateGame = true;
629  }
630  else if (strcmp(arg, "--datadir") == 0)
631  {
632  if (i + 1 < argumentsCount)
633  {
634  ++i;
635  dataDirectories.prepend(arguments[i]);
636  }
637  else
638  {
639  failure = CmdArgsHelp::missingArgs(1, arg);
640  }
641  }
642  else if (strcmp(arg, "--disable-plugin") == 0)
643  {
644  if (i + 1 < argumentsCount)
645  {
646  ++i;
647  pluginsEnabled[QString(arguments[i]).toLower()] = false;
648  }
649  else
650  {
651  failure = CmdArgsHelp::missingArgs(1, arg);
652  }
653  }
654  else if (strcmp(arg, "--enable-plugin") == 0)
655  {
656  if (i + 1 < argumentsCount)
657  {
658  ++i;
659  pluginsEnabled[QString(arguments[i]).toLower()] = true;
660  }
661  else
662  {
663  failure = CmdArgsHelp::missingArgs(1, arg);
664  }
665  }
666  else if (strcmp(arg, "--rcon") == 0)
667  {
668  startRcon = true;
669  if (i + 2 < argumentsCount)
670  {
671  rconPluginName = arguments[++i];
672  Strings::translateServerAddress(arguments[++i], rconAddress, rconPort, "localhost:10666");
673  }
674  }
675  else if (strcmp(arg, "--help") == 0)
676  {
677  gLog.setTimestampsEnabled(false);
678  // Print information to the log and terminate.
680  return false;
681  }
682  else if (strcmp(arg, "--update-failed") == 0)
683  {
684  ++i;
685  updateFailedCode = QString(arguments[i]).toInt();
686  }
687  else if (strcmp(arg, "--portable") == 0)
688  {
689  bPortableMode = true;
690  }
691  else if (strcmp(arg, "--quiet") == 0)
692  {
693  logVerbosity = LV_Quiet;
694  }
695  else if (strcmp(arg, "--tests") == 0)
696  {
697  bTestMode = true;
698  }
699  else if (strcmp(arg, "--verbose") == 0)
700  {
701  logVerbosity = LV_Verbose;
702  }
703  else if (strcmp(arg, "--version-json") == 0)
704  {
705  bVersionDump = true;
706  if (i + 1 < argumentsCount)
707  {
708  ++i;
709  QString filename = arguments[i];
710  if (filename != "-" && filename != "")
711  {
712  versionDumpFile = filename;
713  }
714  }
715  }
716  else
717  {
718  failure = CmdArgsHelp::unrecognizedOption(arg);
719  }
720  }
721 
722  QList<bool> exclusives;
723  exclusives << !connectUrl.isEmpty() << startCreateGame << startRcon;
724  if (exclusives.count(true) > 1)
725  failure = tr("doomseeker: `--connect`, `--create-game` and `--rcon` are mutually exclusive");
726 
727  if (!failure.isEmpty())
728  {
729  gLog.setTimestampsEnabled(false);
730  gLog << failure;
731  return false;
732  }
733  return true;
734 }
735 
736 void Main::setupRefreshingThread()
737 {
738  gLog << tr("Starting refreshing thread.");
739  gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
740  gRefresher->start();
741 }
742 
743 bool Main::shouldLogToStderr() const
744 {
745  if (bTestMode)
746  return logVerbosity != LV_Quiet;
747  if (bVersionDump)
748  return logVerbosity == LV_Verbose;
749  return logVerbosity != LV_Quiet;
750 }
751 
752 //==============================================================================
753 
754 #ifdef _MSC_VER
755  #ifdef NDEBUG
756 #define USE_WINMAIN_AS_ENTRY_POINT
757  #endif
758 #endif
759 
760 #ifdef USE_WINMAIN_AS_ENTRY_POINT
761 #include <windows.h>
762 QStringList getCommandLineArgs()
763 {
764  CommandLineTokenizer tokenizer;
765  return tokenizer.tokenize(QString::fromUtf16((const ushort *)GetCommandLineW()));
766 }
767 
768 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
769 {
770  int argc = 0;
771  char **argv = nullptr;
772 
773  QStringList commandLine = getCommandLineArgs();
774 
775  // At least one is ensured to be here.
776  argc = commandLine.size();
777  argv = new char *[argc];
778 
779  for (int i = 0; i < commandLine.size(); ++i)
780  {
781  const QString &parameter = commandLine[i];
782  argv[i] = new char[parameter.toUtf8().size() + 1];
783  strcpy(argv[i], parameter.toUtf8().constData());
784  }
785 
786  Main *pMain = new Main(argc, argv);
787  int returnValue = pMain->run();
788 
789  // Cleans up after the program.
790  delete pMain;
791 
792  // On the other hand we could just ignore the fact that this array is left
793  // hanging in the memory because Windows will clean it up for us...
794  for (int i = 0; i < argc; ++i)
795  {
796  delete [] argv[i];
797  }
798  delete [] argv;
799 
800  return returnValue;
801 }
802 #else
803 int main(int argc, char *argv[])
804 {
805  Main *pMain = new Main(argc, argv);
806  int returnValue = pMain->run();
807 
808  // Cleans up after the program.
809  delete pMain;
810 
811  return returnValue;
812 }
813 #endif