24 #include <QApplication>
27 #include <QHashIterator>
29 #include <QMainWindow>
30 #include <QMessageBox>
33 #include <QSslConfiguration>
34 #include <QThreadPool>
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"
53 #include "ip2c/ip2c.h"
54 #include "irc/configuration/ircconfig.h"
55 #include "localization.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"
77 Main::Main(
int argc,
char *argv[])
78 : arguments(argv), argumentsCount(argc),
79 startCreateGame(false), startRcon(false)
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",
88 bPortableMode =
false;
90 logVerbosity = LV_Default;
93 qRegisterMetaType<Pattern>(
"Pattern");
94 qRegisterMetaType<ServerPtr>(
"ServerPtr");
96 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
97 qRegisterMetaTypeStreamOperators<Pattern>(
"Pattern");
98 qRegisterMetaType<ServerCPtr>(
"ServerCPtr");
104 if (Application::isInit())
109 if (Refresher::isInstantiated())
111 Refresher::instance()->
quit();
112 Refresher::deinstantiate();
116 if (Application::isInit())
118 gConfig.saveToFile();
121 gIRCConfig.saveToFile();
122 gIRCConfig.dispose();
125 IP2C::deinstantiate();
131 int Main::connectToServerByURL()
137 connect(handler, SIGNAL(finished(
int)), gApp, SLOT(quit()));
139 int ret = gApp->exec();
150 if (!interpretCommandLineParameters())
156 Application::init(argumentsCount, arguments);
159 gApp->setAttribute(Qt::AA_DontShowIconsInMenus);
162 gLog <<
"Starting Doomseeker. Hello World! :)";
163 gLog <<
"Setting up data directories.";
165 if (!initDataDirectories())
174 return runVersionDump();
176 PluginUrlHandler::registerAll();
180 return runTestMode();
184 #ifdef WITH_AUTOUPDATES
194 initLocalizationsDefinitions();
196 initPasswordsConfig();
202 QTimer::singleShot(0,
this, SLOT(runCreateGame()));
206 QTimer::singleShot(0,
this, SLOT(runRemoteConsole()));
208 else if (connectUrl.isValid())
210 setupRefreshingThread();
211 return connectToServerByURL();
215 setupRefreshingThread();
217 #ifdef WITH_AUTOUPDATES
220 if (updateFailedCode != 0)
223 gApp->mainWindow()->setDisplayUpdaterProcessFailure(updateFailedCode);
228 gApp->mainWindow()->setDisplayUpdateInstallerError(updateInstallerResult);
234 QTimer::singleShot(0, gApp->mainWindow(), SLOT(checkForUpdatesAuto()));
240 gLog << tr(
"Init finished.");
241 gLog.addUnformattedEntry(
"================================\n");
243 int returnCode = gApp->exec();
245 #ifdef WITH_AUTOUPDATES
250 updateFailedCode = 0;
251 int installResult = installPendingUpdates();
255 QMessageBox::critical(
nullptr, tr(
"Doomseeker - Updates Install Failure"),
264 int Main::runTestMode()
267 gLog <<
"Entering test mode.";
272 TestRuns::pTestCore = &testCore;
273 TestRuns::callTests();
276 QString strSucceded =
"Tests succeeded: %1";
277 QString strFailed =
"Tests failed: %1";
278 QString strPercentage =
"Pass percentage: %1%";
280 float passPercentage = (float)testCore.numTestsSucceeded() / (float)testCore.numTests();
281 passPercentage *= 100.0f;
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. ====";
289 return testCore.numTestsFailed();
292 int Main::runVersionDump()
296 if (!versionDumpFile.isEmpty())
298 outfile.setFileName(versionDumpFile);
299 if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
301 error = tr(
"Failed to open file '%1'.").arg(versionDumpFile);
307 if (!outfile.open(stdout, QIODevice::WriteOnly))
309 error = tr(
"Failed to open stdout.");
312 if (!error.isEmpty())
314 gLog.setPrintingToStderr(
true);
319 gLog << tr(
"Dumping version info to file in JSON format.");
320 VersionDump::dumpJsonToIO(outfile);
324 void Main::applyLogVerbosity()
326 gLog.setPrintingToStderr(shouldLogToStderr());
329 void Main::createMainWindow()
331 gLog << tr(
"Preparing GUI.");
334 gApp->mainWindow()->show();
338 gApp->mainWindow()->notifyFirstRun();
342 void Main::runCreateGame()
344 gLog << tr(
"Starting Create Game box.");
346 dialog->setWindowIcon(Application::icon());
350 void Main::runRemoteConsole()
352 gLog << tr(
"Starting RCon client.");
353 if (rconPluginName.isEmpty())
355 bool canAnyEngineRcon =
false;
356 for (
unsigned int i = 0; i < gPlugins->numPlugins(); i++)
359 if (info->
server(QHostAddress(
"localhost"), 0)->hasRcon())
361 canAnyEngineRcon =
true;
365 if (!canAnyEngineRcon)
367 QString error = tr(
"None of the currently loaded game plugins supports RCon.");
369 QMessageBox::critical(
nullptr, tr(
"Doomseeker RCon"), error);
380 int pIndex = gPlugins->pluginIndexFromName(rconPluginName);
383 gLog << tr(
"Couldn't find the specified plugin: ") + rconPluginName;
389 const EnginePlugin *plugin = gPlugins->plugin(pIndex)->info();
390 ServerPtr server = plugin->
server(QHostAddress(rconAddress), rconPort);
391 if (!server->hasRcon())
393 gLog << tr(
"Plugin does not support RCon.");
404 void Main::initCaCerts()
406 QString certsFilePath = DoomseekerFilePaths::cacerts();
407 QFile certsFile(certsFilePath);
408 if (!certsFilePath.isEmpty() && certsFile.exists())
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);
423 bool Main::initDataDirectories()
427 gLog << tr(
"Running in the portable mode. Forcing current directory to: %1")
428 .arg(QCoreApplication::applicationDirPath());
429 if (!QDir::setCurrent(QCoreApplication::applicationDirPath()))
431 gLog << tr(
"Forcing the current directory failed! Path detection may misbehave.");
435 DataPaths::initDefault(bPortableMode);
436 if (!baseDir.isEmpty())
437 gDefaultDataPaths->setBaseDir(baseDir);
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());
443 DoomseekerFilePaths::pDataPaths = gDefaultDataPaths;
444 QList<DirErrno> failedDirsErrno = gDefaultDataPaths->createDirectories();
445 if (!failedDirsErrno.isEmpty())
449 QString errorMessage = tr(
"Doomseeker will not run because some directories cannot be used properly.\n");
450 for (
const DirErrno &failedDirErrno : failedDirsErrno)
452 errorMessage +=
"\n[" + QString::number(failedDirErrno.errnoNum) +
"] ";
453 errorMessage += failedDirErrno.directory.absolutePath() +
": ";
454 errorMessage += failedDirErrno.errnoString;
457 QMessageBox::critical(
nullptr, tr(
"Doomseeker startup error"), errorMessage);
463 dataDirectories << gDefaultDataPaths->localDataLocationPath();
464 dataDirectories << QCoreApplication::applicationDirPath();
467 dataDirectories <<
"./";
468 #if defined(Q_OS_LINUX)
470 dataDirectories << INSTALL_PREFIX
"/share/doomseeker/";
473 dataDirectories <<
":/";
474 QDir::setSearchPaths(
"data", dataDirectories);
481 gLog << tr(
"Initializing IP2C database.");
487 void Main::initIRCConfig()
489 gLog << tr(
"Initializing IRC configuration file.");
495 QString configPath = DoomseekerFilePaths::ircIni();
496 if (!configPath.isEmpty())
498 if (gIRCConfig.setIniFile(configPath))
500 gIRCConfig.readFromFile();
505 void Main::initLocalizationsDefinitions()
507 gLog << tr(
"Loading translations definitions");
508 Localization::get()->loadLocalizationsList(
512 gConfig.doomseeker.localization);
515 gLog << tr(
"Loading translation \"%1\".").arg(bestMatchedLocalization.
localeName);
516 bool bSuccess = Localization::get()->loadTranslation(bestMatchedLocalization.
localeName);
519 gLog << tr(
"Translation loaded.");
523 gLog << tr(
"Failed to load translation.");
528 void Main::initMainConfig()
530 gLog << tr(
"Initializing configuration file.");
536 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
537 if (configDirPath.isEmpty())
539 gLog << tr(
"Could not get an access to the settings directory. Configuration will not be saved.");
543 QString filePath = DoomseekerFilePaths::ini();
546 QFileInfo iniFileInfo(filePath);
547 bIsFirstRun = !iniFileInfo.exists();
550 if (gConfig.setIniFile(filePath))
552 gConfig.readFromFile();
556 void Main::initPasswordsConfig()
558 gLog << tr(
"Initializing passwords configuration file.");
560 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
561 if (configDirPath.isEmpty())
565 QString filePath = DoomseekerFilePaths::passwordIni();
566 PasswordsCfg::initIni(filePath);
569 void Main::initPluginConfig()
571 gLog << tr(
"Initializing configuration for plugins.");
572 gPlugins->initConfig();
575 int Main::installPendingUpdates()
578 if (gConfig.autoUpdates.bPerformUpdateOnNextRun)
580 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
581 gConfig.saveToFile();
584 if (updateFailedCode == 0)
590 return updateInstallerResult;
593 bool Main::interpretCommandLineParameters()
597 for (
int i = 1; i < argumentsCount && failure.isEmpty(); ++i)
599 const char *arg = arguments[i];
601 if (strcmp(arg,
"--basedir") == 0)
603 if (i + 1 < argumentsCount)
606 baseDir = arguments[i];
613 else if (strcmp(arg,
"--connect") == 0)
615 if (i + 1 < argumentsCount)
618 connectUrl = QUrl(arguments[i]);
626 else if (strcmp(arg,
"--create-game") == 0)
628 startCreateGame =
true;
630 else if (strcmp(arg,
"--datadir") == 0)
632 if (i + 1 < argumentsCount)
635 dataDirectories.prepend(arguments[i]);
642 else if (strcmp(arg,
"--disable-plugin") == 0)
644 if (i + 1 < argumentsCount)
647 pluginsEnabled[QString(arguments[i]).toLower()] =
false;
654 else if (strcmp(arg,
"--enable-plugin") == 0)
656 if (i + 1 < argumentsCount)
659 pluginsEnabled[QString(arguments[i]).toLower()] =
true;
666 else if (strcmp(arg,
"--rcon") == 0)
669 if (i + 2 < argumentsCount)
671 rconPluginName = arguments[++i];
675 else if (strcmp(arg,
"--help") == 0)
677 gLog.setTimestampsEnabled(
false);
682 else if (strcmp(arg,
"--update-failed") == 0)
685 updateFailedCode = QString(arguments[i]).toInt();
687 else if (strcmp(arg,
"--portable") == 0)
689 bPortableMode =
true;
691 else if (strcmp(arg,
"--quiet") == 0)
693 logVerbosity = LV_Quiet;
695 else if (strcmp(arg,
"--tests") == 0)
699 else if (strcmp(arg,
"--verbose") == 0)
701 logVerbosity = LV_Verbose;
703 else if (strcmp(arg,
"--version-json") == 0)
706 if (i + 1 < argumentsCount)
709 QString filename = arguments[i];
710 if (filename !=
"-" && filename !=
"")
712 versionDumpFile = filename;
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");
727 if (!failure.isEmpty())
729 gLog.setTimestampsEnabled(
false);
736 void Main::setupRefreshingThread()
738 gLog << tr(
"Starting refreshing thread.");
739 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
743 bool Main::shouldLogToStderr()
const
746 return logVerbosity != LV_Quiet;
748 return logVerbosity == LV_Verbose;
749 return logVerbosity != LV_Quiet;
756 #define USE_WINMAIN_AS_ENTRY_POINT
760 #ifdef USE_WINMAIN_AS_ENTRY_POINT
762 QStringList getCommandLineArgs()
765 return tokenizer.tokenize(QString::fromUtf16((
const ushort *)GetCommandLineW()));
768 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine,
int nCmdShow)
771 char **argv =
nullptr;
773 QStringList commandLine = getCommandLineArgs();
776 argc = commandLine.size();
777 argv =
new char *[argc];
779 for (
int i = 0; i < commandLine.size(); ++i)
781 const QString ¶meter = commandLine[i];
782 argv[i] =
new char[parameter.toUtf8().size() + 1];
783 strcpy(argv[i], parameter.toUtf8().constData());
787 int returnValue = pMain->
run();
794 for (
int i = 0; i < argc; ++i)
803 int main(
int argc,
char *argv[])
806 int returnValue = pMain->
run();