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