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 sendfd recvfd tape 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 238 if (bInstallUpdatesAndRestart)
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();
static void translateServerAddress(const QString &addressString, QString &hostname, unsigned short &port, const QString &defaultAddress)
Translates string in format "hostname:port" to atomic values.
static QString unrecognizedOption(QString option)
Returns a string informing about the use of an unknown option, followed by availableCommands().
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().
static bool bInstallUpdatesAndRestart
If true then program will install updates and restart instead of quitting if quit is requested...
static QString missingArgs(int expectedArguments, QString option)
Returns a string informing about the lack of arguments, followed by availableCommands().
int run()
Replaces main().
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...
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.
Core for developer tests.
static const LocalizationInfo PROGRAM_NATIVE
static QStringList staticDataSearchDirs(const QString &subdir=QString())
Paths to directories where program should search for its static data.