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 "gui/createserverdialog.h"
49 #include "gui/mainwindow.h"
50 #include "gui/remoteconsole.h"
52 #include "ip2c/ip2c.h"
53 #include "irc/configuration/ircconfig.h"
54 #include "localization.h"
58 #include "plugins/engineplugin.h"
59 #include "plugins/pluginloader.h"
60 #include "refresher/refresher.h"
61 #include "serverapi/server.h"
62 #include "serverapi/server.h"
63 #include "strings.hpp"
64 #include "tests/testruns.h"
65 #include "updater/updateinstaller.h"
66 #include "versiondump.h"
67 #include "wadseeker/wadseeker.h"
73 QString Main::argDataDir;
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())
173 return runVersionDump();
175 PluginUrlHandler::registerAll();
179 return runTestMode();
183 #ifdef WITH_AUTOUPDATES
193 initLocalizationsDefinitions();
195 initPasswordsConfig();
201 QTimer::singleShot(0,
this, SLOT(runCreateGame()));
205 QTimer::singleShot(0,
this, SLOT(runRemoteConsole()));
207 else if (connectUrl.isValid())
209 setupRefreshingThread();
210 return connectToServerByURL();
214 setupRefreshingThread();
216 #ifdef WITH_AUTOUPDATES
219 if (updateFailedCode != 0)
222 gApp->mainWindow()->setDisplayUpdaterProcessFailure(updateFailedCode);
227 gApp->mainWindow()->setDisplayUpdateInstallerError(updateInstallerResult);
233 QTimer::singleShot(0, gApp->mainWindow(), SLOT(checkForUpdatesAuto()));
239 gLog << tr(
"Init finished.");
240 gLog.addUnformattedEntry(
"================================\n");
242 int returnCode = gApp->exec();
244 #ifdef WITH_AUTOUPDATES
249 updateFailedCode = 0;
250 int installResult = installPendingUpdates();
254 QMessageBox::critical(
nullptr, tr(
"Doomseeker - Updates Install Failure"),
263 int Main::runTestMode()
266 gLog <<
"Entering test mode.";
271 TestRuns::pTestCore = &testCore;
272 TestRuns::callTests();
275 QString strSucceded =
"Tests succeeded: %1";
276 QString strFailed =
"Tests failed: %1";
277 QString strPercentage =
"Pass percentage: %1%";
279 float passPercentage = (float)testCore.numTestsSucceeded() / (float)testCore.numTests();
280 passPercentage *= 100.0f;
282 gLog <<
"==== TESTS SUMMARY: ====";
283 gLog << strSucceded.arg(testCore.numTestsSucceeded(), 6);
284 gLog << strFailed.arg(testCore.numTestsFailed(), 6);
285 gLog << strPercentage.arg(passPercentage, 6,
'f', 2);
286 gLog <<
"==== Done. ====";
288 return testCore.numTestsFailed();
291 int Main::runVersionDump()
295 if (!versionDumpFile.isEmpty())
297 outfile.setFileName(versionDumpFile);
298 if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
300 error = tr(
"Failed to open file '%1'.").arg(versionDumpFile);
306 if (!outfile.open(stdout, QIODevice::WriteOnly))
308 error = tr(
"Failed to open stdout.");
311 if (!error.isEmpty())
313 gLog.setPrintingToStderr(
true);
318 gLog << tr(
"Dumping version info to file in JSON format.");
319 VersionDump::dumpJsonToIO(outfile);
323 void Main::applyLogVerbosity()
325 gLog.setPrintingToStderr(shouldLogToStderr());
328 void Main::createMainWindow()
330 gLog << tr(
"Preparing GUI.");
333 gApp->mainWindow()->show();
337 gApp->mainWindow()->notifyFirstRun();
341 void Main::runCreateGame()
343 gLog << tr(
"Starting Create Game box.");
345 dialog->setWindowIcon(Application::icon());
349 void Main::runRemoteConsole()
351 gLog << tr(
"Starting RCon client.");
352 if (rconPluginName.isEmpty())
354 bool canAnyEngineRcon =
false;
355 for (
unsigned int i = 0; i < gPlugins->numPlugins(); i++)
358 if (info->
server(QHostAddress(
"localhost"), 0)->hasRcon())
360 canAnyEngineRcon =
true;
364 if (!canAnyEngineRcon)
366 QString error = tr(
"None of the currently loaded game plugins supports RCon.");
368 QMessageBox::critical(
nullptr, tr(
"Doomseeker RCon"), error);
379 int pIndex = gPlugins->pluginIndexFromName(rconPluginName);
382 gLog << tr(
"Couldn't find the specified plugin: ") + rconPluginName;
388 const EnginePlugin *plugin = gPlugins->plugin(pIndex)->info();
389 ServerPtr server = plugin->
server(QHostAddress(rconAddress), rconPort);
390 if (!server->hasRcon())
392 gLog << tr(
"Plugin does not support RCon.");
403 void Main::initCaCerts()
405 QString certsFilePath = DoomseekerFilePaths::cacerts();
406 QFile certsFile(certsFilePath);
407 if (!certsFilePath.isEmpty() && certsFile.exists())
409 gLog << tr(
"Loading extra CA certificates from '%1'.").arg(certsFilePath);
410 certsFile.open(QIODevice::ReadOnly);
411 QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
412 QList<QSslCertificate> cacerts = sslConf.caCertificates();
413 QList<QSslCertificate> extraCerts = QSslCertificate::fromDevice(&certsFile);
414 gLog << tr(
"Appending %n extra CA certificate(s).",
nullptr, extraCerts.size());
415 cacerts.append(extraCerts);
416 sslConf.setCaCertificates(cacerts);
417 QSslConfiguration::setDefaultConfiguration(sslConf);
422 bool Main::initDataDirectories()
424 DataPaths::initDefault(bPortableMode);
425 DoomseekerFilePaths::pDataPaths = gDefaultDataPaths;
426 QList<DataPaths::DirErrno> failedDirsErrno = gDefaultDataPaths->createDirectories();
427 if (!failedDirsErrno.isEmpty())
431 QString errorMessage = tr(
"Doomseeker will not run because some directories cannot be used properly.\n");
434 errorMessage +=
"\n[" + QString::number(failedDirErrno.errnoNum) +
"] ";
435 errorMessage += failedDirErrno.directory.absolutePath() +
": ";
436 errorMessage += failedDirErrno.errnoString;
439 QMessageBox::critical(
nullptr, tr(
"Doomseeker startup error"), errorMessage);
445 dataDirectories << gDefaultDataPaths->localDataLocationPath();
446 dataDirectories << gDefaultDataPaths->workingDirectory();
449 dataDirectories <<
"./";
450 #if defined(Q_OS_LINUX)
452 dataDirectories << INSTALL_PREFIX
"/share/doomseeker/";
455 dataDirectories <<
":/";
456 QDir::setSearchPaths(
"data", dataDirectories);
463 gLog << tr(
"Initializing IP2C database.");
469 void Main::initIRCConfig()
471 gLog << tr(
"Initializing IRC configuration file.");
477 QString configPath = DoomseekerFilePaths::ircIni();
478 if (!configPath.isEmpty())
480 if (gIRCConfig.setIniFile(configPath))
482 gIRCConfig.readFromFile();
487 void Main::initLocalizationsDefinitions()
489 gLog << tr(
"Loading translations definitions");
490 Localization::get()->loadLocalizationsList(
494 gConfig.doomseeker.localization);
497 gLog << tr(
"Loading translation \"%1\".").arg(bestMatchedLocalization.
localeName);
498 bool bSuccess = Localization::get()->loadTranslation(bestMatchedLocalization.
localeName);
501 gLog << tr(
"Translation loaded.");
505 gLog << tr(
"Failed to load translation.");
510 void Main::initMainConfig()
512 gLog << tr(
"Initializing configuration file.");
518 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
519 if (configDirPath.isEmpty())
521 gLog << tr(
"Could not get an access to the settings directory. Configuration will not be saved.");
525 QString filePath = DoomseekerFilePaths::ini();
528 QFileInfo iniFileInfo(filePath);
529 bIsFirstRun = !iniFileInfo.exists();
532 if (gConfig.setIniFile(filePath))
534 gConfig.readFromFile();
538 void Main::initPasswordsConfig()
540 gLog << tr(
"Initializing passwords configuration file.");
542 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
543 if (configDirPath.isEmpty())
547 QString filePath = DoomseekerFilePaths::passwordIni();
548 PasswordsCfg::initIni(filePath);
551 void Main::initPluginConfig()
553 gLog << tr(
"Initializing configuration for plugins.");
554 gPlugins->initConfig();
557 int Main::installPendingUpdates()
560 if (gConfig.autoUpdates.bPerformUpdateOnNextRun)
562 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
563 gConfig.saveToFile();
566 if (updateFailedCode == 0)
572 return updateInstallerResult;
575 bool Main::interpretCommandLineParameters()
579 for (
int i = 1; i < argumentsCount && failure.isEmpty(); ++i)
581 const char *arg = arguments[i];
583 if (strcmp(arg,
"--connect") == 0)
585 if (i + 1 < argumentsCount)
588 connectUrl = QUrl(arguments[i]);
596 else if (strcmp(arg,
"--create-game") == 0)
598 startCreateGame =
true;
600 else if (strcmp(arg,
"--datadir") == 0)
602 if (i + 1 < argumentsCount)
605 dataDirectories.prepend(arguments[i]);
606 argDataDir = arguments[i];
613 else if (strcmp(arg,
"--rcon") == 0)
616 if (i + 2 < argumentsCount)
618 rconPluginName = arguments[++i];
622 else if (strcmp(arg,
"--help") == 0)
624 gLog.setTimestampsEnabled(
false);
629 else if (strcmp(arg,
"--update-failed") == 0)
632 updateFailedCode = QString(arguments[i]).toInt();
634 else if (strcmp(arg,
"--portable") == 0)
636 bPortableMode =
true;
638 else if (strcmp(arg,
"--quiet") == 0)
640 logVerbosity = LV_Quiet;
642 else if (strcmp(arg,
"--tests") == 0)
646 else if (strcmp(arg,
"--verbose") == 0)
648 logVerbosity = LV_Verbose;
650 else if (strcmp(arg,
"--version-json") == 0)
653 if (i + 1 < argumentsCount)
656 QString filename = arguments[i];
657 if (filename !=
"-" && filename !=
"")
659 versionDumpFile = filename;
669 QList<bool> exclusives;
670 exclusives << !connectUrl.isEmpty() << startCreateGame << startRcon;
671 if (exclusives.count(
true) > 1)
672 failure = tr(
"doomseeker: `--connect`, `--create-game` and `--rcon` are mutually exclusive");
674 if (!failure.isEmpty())
676 gLog.setTimestampsEnabled(
false);
683 void Main::setupRefreshingThread()
685 gLog << tr(
"Starting refreshing thread.");
686 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
690 bool Main::shouldLogToStderr()
const
693 return logVerbosity != LV_Quiet;
695 return logVerbosity == LV_Verbose;
696 return logVerbosity != LV_Quiet;
703 #define USE_WINMAIN_AS_ENTRY_POINT
707 #ifdef USE_WINMAIN_AS_ENTRY_POINT
709 QStringList getCommandLineArgs()
712 return tokenizer.tokenize(QString::fromUtf16((
const ushort *)GetCommandLineW()));
715 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine,
int nCmdShow)
718 char **argv =
nullptr;
720 QStringList commandLine = getCommandLineArgs();
723 argc = commandLine.size();
724 argv =
new char *[argc];
726 for (
int i = 0; i < commandLine.size(); ++i)
728 const QString ¶meter = commandLine[i];
729 argv[i] =
new char[parameter.toUtf8().size() + 1];
730 strcpy(argv[i], parameter.toUtf8().constData());
734 int returnValue = pMain->
run();
741 for (
int i = 0; i < argc; ++i)
750 int main(
int argc,
char *argv[])
753 int returnValue = pMain->
run();