24 #include <QApplication>
27 #include <QHashIterator>
29 #include <QMainWindow>
30 #include <QMessageBox>
32 #include <QSslConfiguration>
33 #include <QThreadPool>
38 #include "application.h"
39 #include "cmdargshelp.h"
40 #include "commandlinetokenizer.h"
41 #include "configuration/doomseekerconfig.h"
42 #include "configuration/passwordscfg.h"
43 #include "configuration/queryspeed.h"
44 #include "connectionhandler.h"
45 #include "datapaths.h"
46 #include "doomseekerfilepaths.h"
47 #include "gui/createserverdialog.h"
48 #include "gui/mainwindow.h"
49 #include "gui/remoteconsole.h"
51 #include "ip2c/ip2c.h"
52 #include "irc/configuration/ircconfig.h"
53 #include "localization.h"
56 #include "plugins/engineplugin.h"
57 #include "plugins/pluginloader.h"
58 #include "refresher/refresher.h"
59 #include "serverapi/server.h"
60 #include "serverapi/server.h"
61 #include "strings.hpp"
62 #include "tests/testruns.h"
63 #include "updater/updateinstaller.h"
64 #include "versiondump.h"
65 #include "wadseeker/wadseeker.h"
71 QString Main::argDataDir;
75 Main::Main(
int argc,
char *argv[])
76 : arguments(argv), argumentsCount(argc),
77 startCreateGame(false), startRcon(false)
80 pledge (
"stdio rpath wpath cpath tmppath inet mcast fattr chown flock unix "
81 "dns getpw sendfd recvfd tty proc exec prot_exec ps audio video unveil",
86 bPortableMode =
false;
88 logVerbosity = LV_Default;
91 qRegisterMetaType<ServerPtr>(
"ServerPtr");
92 qRegisterMetaType<ServerCPtr>(
"ServerCPtr");
97 if (Application::isInit())
102 if (Refresher::isInstantiated())
104 Refresher::instance()->
quit();
105 Refresher::deinstantiate();
109 if (Application::isInit())
111 gConfig.saveToFile();
114 gIRCConfig.saveToFile();
115 gIRCConfig.dispose();
118 IP2C::deinstantiate();
124 int Main::connectToServerByURL()
130 connect(handler, SIGNAL(finished(
int)), gApp, SLOT(quit()));
132 int ret = gApp->exec();
143 if (!interpretCommandLineParameters())
149 Application::init(argumentsCount, arguments);
152 gApp->setAttribute(Qt::AA_DontShowIconsInMenus);
155 gLog <<
"Starting Doomseeker. Hello World! :)";
156 gLog <<
"Setting up data directories.";
158 if (!initDataDirectories())
166 return runVersionDump();
168 PluginUrlHandler::registerAll();
172 return runTestMode();
176 #ifdef WITH_AUTOUPDATES
186 initLocalizationsDefinitions();
188 initPasswordsConfig();
194 QTimer::singleShot(0,
this, SLOT(runCreateGame()));
198 QTimer::singleShot(0,
this, SLOT(runRemoteConsole()));
200 else if (connectUrl.isValid())
202 setupRefreshingThread();
203 return connectToServerByURL();
207 setupRefreshingThread();
209 #ifdef WITH_AUTOUPDATES
212 if (updateFailedCode != 0)
215 gApp->mainWindow()->setDisplayUpdaterProcessFailure(updateFailedCode);
220 gApp->mainWindow()->setDisplayUpdateInstallerError(updateInstallerResult);
226 QTimer::singleShot(0, gApp->mainWindow(), SLOT(checkForUpdatesAuto()));
232 gLog << tr(
"Init finished.");
233 gLog.addUnformattedEntry(
"================================\n");
235 int returnCode = gApp->exec();
237 #ifdef WITH_AUTOUPDATES
242 updateFailedCode = 0;
243 int installResult = installPendingUpdates();
247 QMessageBox::critical(
nullptr, tr(
"Doomseeker - Updates Install Failure"),
256 int Main::runTestMode()
259 gLog <<
"Entering test mode.";
264 TestRuns::pTestCore = &testCore;
265 TestRuns::callTests();
268 QString strSucceded =
"Tests succeeded: %1";
269 QString strFailed =
"Tests failed: %1";
270 QString strPercentage =
"Pass percentage: %1%";
272 float passPercentage = (float)testCore.numTestsSucceeded() / (float)testCore.numTests();
273 passPercentage *= 100.0f;
275 gLog <<
"==== TESTS SUMMARY: ====";
276 gLog << strSucceded.arg(testCore.numTestsSucceeded(), 6);
277 gLog << strFailed.arg(testCore.numTestsFailed(), 6);
278 gLog << strPercentage.arg(passPercentage, 6,
'f', 2);
279 gLog <<
"==== Done. ====";
281 return testCore.numTestsFailed();
284 int Main::runVersionDump()
288 if (!versionDumpFile.isEmpty())
290 outfile.setFileName(versionDumpFile);
291 if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
293 error = tr(
"Failed to open file '%1'.").arg(versionDumpFile);
299 if (!outfile.open(stdout, QIODevice::WriteOnly))
301 error = tr(
"Failed to open stdout.");
304 if (!error.isEmpty())
306 gLog.setPrintingToStderr(
true);
311 gLog << tr(
"Dumping version info to file in JSON format.");
312 VersionDump::dumpJsonToIO(outfile);
316 void Main::applyLogVerbosity()
318 gLog.setPrintingToStderr(shouldLogToStderr());
321 void Main::createMainWindow()
323 gLog << tr(
"Preparing GUI.");
326 gApp->mainWindow()->show();
330 gApp->mainWindow()->notifyFirstRun();
334 void Main::runCreateGame()
336 gLog << tr(
"Starting Create Game box.");
338 dialog->setWindowIcon(Application::icon());
342 void Main::runRemoteConsole()
344 gLog << tr(
"Starting RCon client.");
345 if (rconPluginName.isEmpty())
347 bool canAnyEngineRcon =
false;
348 for (
unsigned int i = 0; i < gPlugins->numPlugins(); i++)
351 if (info->
server(QHostAddress(
"localhost"), 0)->hasRcon())
353 canAnyEngineRcon =
true;
357 if (!canAnyEngineRcon)
359 QString error = tr(
"None of the currently loaded game plugins supports RCon.");
361 QMessageBox::critical(
nullptr, tr(
"Doomseeker RCon"), error);
372 int pIndex = gPlugins->pluginIndexFromName(rconPluginName);
375 gLog << tr(
"Couldn't find specified plugin: ") + rconPluginName;
381 const EnginePlugin *plugin = gPlugins->plugin(pIndex)->info();
382 ServerPtr server = plugin->
server(QHostAddress(rconAddress), rconPort);
383 if (!server->hasRcon())
385 gLog << tr(
"Plugin does not support RCon.");
396 void Main::initCaCerts()
398 QString certsFilePath = DoomseekerFilePaths::cacerts();
399 QFile certsFile(certsFilePath);
400 if (!certsFilePath.isEmpty() && certsFile.exists())
402 gLog << tr(
"Loading extra CA certificates from '%1'.").arg(certsFilePath);
403 certsFile.open(QIODevice::ReadOnly);
404 QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
405 QList<QSslCertificate> cacerts = sslConf.caCertificates();
406 QList<QSslCertificate> extraCerts = QSslCertificate::fromDevice(&certsFile);
407 gLog << tr(
"Appending %n extra CA certificate(s).",
nullptr, extraCerts.size());
408 cacerts.append(extraCerts);
409 sslConf.setCaCertificates(cacerts);
410 QSslConfiguration::setDefaultConfiguration(sslConf);
415 bool Main::initDataDirectories()
417 DataPaths::initDefault(bPortableMode);
418 DoomseekerFilePaths::pDataPaths = gDefaultDataPaths;
419 QList<DataPaths::DirErrno> failedDirsErrno = gDefaultDataPaths->createDirectories();
420 if (!failedDirsErrno.isEmpty())
424 QString errorMessage = tr(
"Doomseeker will not run because some directories cannot be used properly.\n");
427 errorMessage +=
"\n[" + QString::number(failedDirErrno.errnoNum) +
"] ";
428 errorMessage += failedDirErrno.directory.absolutePath() +
": ";
429 errorMessage += failedDirErrno.errnoString;
432 QMessageBox::critical(
nullptr, tr(
"Doomseeker startup error"), errorMessage);
438 dataDirectories << gDefaultDataPaths->localDataLocationPath();
439 dataDirectories << gDefaultDataPaths->workingDirectory();
442 dataDirectories <<
"./";
443 #if defined(Q_OS_LINUX)
445 dataDirectories << INSTALL_PREFIX
"/share/doomseeker/";
448 dataDirectories <<
":/";
449 QDir::setSearchPaths(
"data", dataDirectories);
456 gLog << tr(
"Initializing IP2C database.");
462 void Main::initIRCConfig()
464 gLog << tr(
"Initializing IRC configuration file.");
470 QString configPath = DoomseekerFilePaths::ircIni();
471 if (!configPath.isEmpty())
473 if (gIRCConfig.setIniFile(configPath))
475 gIRCConfig.readFromFile();
480 void Main::initLocalizationsDefinitions()
482 gLog << tr(
"Loading translations definitions");
483 Localization::get()->loadLocalizationsList(
487 gConfig.doomseeker.localization);
490 gLog << tr(
"Loading translation \"%1\".").arg(bestMatchedLocalization.
localeName);
491 bool bSuccess = Localization::get()->loadTranslation(bestMatchedLocalization.
localeName);
494 gLog << tr(
"Translation loaded.");
498 gLog << tr(
"Failed to load translation.");
503 void Main::initMainConfig()
505 gLog << tr(
"Initializing configuration file.");
511 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
512 if (configDirPath.isEmpty())
514 gLog << tr(
"Could not get an access to the settings directory. Configuration will not be saved.");
518 QString filePath = DoomseekerFilePaths::ini();
521 QFileInfo iniFileInfo(filePath);
522 bIsFirstRun = !iniFileInfo.exists();
525 if (gConfig.setIniFile(filePath))
527 gConfig.readFromFile();
531 void Main::initPasswordsConfig()
533 gLog << tr(
"Initializing passwords configuration file.");
535 QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
536 if (configDirPath.isEmpty())
540 QString filePath = DoomseekerFilePaths::passwordIni();
541 PasswordsCfg::initIni(filePath);
544 void Main::initPluginConfig()
546 gLog << tr(
"Initializing configuration for plugins.");
547 gPlugins->initConfig();
550 int Main::installPendingUpdates()
553 if (gConfig.autoUpdates.bPerformUpdateOnNextRun)
555 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
556 gConfig.saveToFile();
559 if (updateFailedCode == 0)
565 return updateInstallerResult;
568 bool Main::interpretCommandLineParameters()
572 for (
int i = 1; i < argumentsCount && failure.isEmpty(); ++i)
574 const char *arg = arguments[i];
576 if (strcmp(arg,
"--connect") == 0)
578 if (i + 1 < argumentsCount)
581 connectUrl = QUrl(arguments[i]);
589 else if (strcmp(arg,
"--create-game") == 0)
591 startCreateGame =
true;
593 else if (strcmp(arg,
"--datadir") == 0)
595 if (i + 1 < argumentsCount)
598 dataDirectories.prepend(arguments[i]);
599 argDataDir = arguments[i];
606 else if (strcmp(arg,
"--rcon") == 0)
609 if (i + 2 < argumentsCount)
611 rconPluginName = arguments[++i];
615 else if (strcmp(arg,
"--help") == 0)
617 gLog.setTimestampsEnabled(
false);
622 else if (strcmp(arg,
"--update-failed") == 0)
625 updateFailedCode = QString(arguments[i]).toInt();
627 else if (strcmp(arg,
"--portable") == 0)
629 bPortableMode =
true;
631 else if (strcmp(arg,
"--quiet") == 0)
633 logVerbosity = LV_Quiet;
635 else if (strcmp(arg,
"--tests") == 0)
639 else if (strcmp(arg,
"--verbose") == 0)
641 logVerbosity = LV_Verbose;
643 else if (strcmp(arg,
"--version-json") == 0)
646 if (i + 1 < argumentsCount)
649 QString filename = arguments[i];
650 if (filename !=
"-" && filename !=
"")
652 versionDumpFile = filename;
662 QList<bool> exclusives;
663 exclusives << !connectUrl.isEmpty() << startCreateGame << startRcon;
664 if (exclusives.count(
true) > 1)
665 failure = tr(
"doomseeker: `--connect`, `--create-game` and `--rcon` are mutually exclusive");
667 if (!failure.isEmpty())
669 gLog.setTimestampsEnabled(
false);
676 void Main::setupRefreshingThread()
678 gLog << tr(
"Starting refreshing thread.");
679 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
683 bool Main::shouldLogToStderr()
const
686 return logVerbosity != LV_Quiet;
688 return logVerbosity == LV_Verbose;
689 return logVerbosity != LV_Quiet;
696 #define USE_WINMAIN_AS_ENTRY_POINT
700 #ifdef USE_WINMAIN_AS_ENTRY_POINT
702 QStringList getCommandLineArgs()
705 return tokenizer.tokenize(QString::fromUtf16((
const ushort *)GetCommandLineW()));
708 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine,
int nCmdShow)
711 char **argv =
nullptr;
713 QStringList commandLine = getCommandLineArgs();
716 argc = commandLine.size();
717 argv =
new char *[argc];
719 for (
int i = 0; i < commandLine.size(); ++i)
721 const QString ¶meter = commandLine[i];
722 argv[i] =
new char[parameter.toUtf8().size() + 1];
723 strcpy(argv[i], parameter.toUtf8().constData());
727 int returnValue = pMain->
run();
734 for (
int i = 0; i < argc; ++i)
743 int main(
int argc,
char *argv[])
746 int returnValue = pMain->
run();