gameclientrunner.cpp
1 //------------------------------------------------------------------------------
2 // gameclientrunner.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) 2010 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "gameclientrunner.h"
24 
25 #include "apprunner.h"
26 #include "commandlinetokenizer.h"
27 #include "configuration/doomseekerconfig.h"
28 #include "gui/checkwadsdlg.h"
29 #include "ini/inisection.h"
30 #include "ini/inivariable.h"
31 #include "log.h"
32 #include "pathfinder/pathfinder.h"
33 #include "pathfinder/wadpathfinder.h"
34 #include "plugins/engineplugin.h"
35 #include "serverapi/exefile.h"
36 #include "serverapi/gameexeretriever.h"
37 #include "serverapi/message.h"
38 #include "serverapi/server.h"
39 #include <QDir>
40 #include <QScopedPointer>
41 #include <QStringList>
42 
43 DClass<ServerConnectParams>
44 {
45 public:
46  QString connectPassword;
47  QString demoName;
48  QString inGamePassword;
49 };
50 
51 DPointered(ServerConnectParams)
52 
54 {
55 }
56 
57 ServerConnectParams::ServerConnectParams(const ServerConnectParams &other)
58 {
59  d = other.d;
60 }
61 
62 ServerConnectParams &ServerConnectParams::operator=(const ServerConnectParams &other)
63 {
64  d = other.d;
65  return *this;
66 }
67 
68 ServerConnectParams::~ServerConnectParams()
69 {
70 }
71 
73 {
74  return d->connectPassword;
75 }
76 
77 const QString &ServerConnectParams::demoName() const
78 {
79  return d->demoName;
80 }
81 
83 {
84  return d->inGamePassword;
85 }
86 
87 void ServerConnectParams::setConnectPassword(const QString &val)
88 {
89  d->connectPassword = val;
90 }
91 
92 void ServerConnectParams::setDemoName(const QString &val)
93 {
94  d->demoName = val;
95 }
96 
97 void ServerConnectParams::setInGamePassword(const QString &val)
98 {
99  d->inGamePassword = val;
100 }
102 #define BAIL_ON_ERROR(method) \
103  { \
104  method; \
105  if (isFatalError()) \
106  { \
107  return; \
108  } \
109  }
110 
111 
112 DClass<GameClientRunner>
113 {
114 public:
115  QString argBexLoading;
116  QString argConnect;
117  QString argConnectPassword;
118  QString argDehLoading;
119  QString argInGamePassword;
120  QString argIwadLoading;
121  QString argOptionalWadLoading;
122  QString argPort;
123  QString argPwadLoading;
124  QString argDemoRecord;
125 
126  QStringList args;
127  mutable QString cachedIwadPath;
128  ServerConnectParams connectParams;
129  CommandLineInfo *cli;
130  JoinError joinError;
131  QList<PWad> missingPwads;
132  QList<PWad> incompatiblePwads;
133  PathFinder pathFinder;
134  ServerPtr server;
135 
136  void (GameClientRunner::*addConnectCommand)();
137  void (GameClientRunner::*addExtra)();
138  void (GameClientRunner::*addGamePaths)();
139  void (GameClientRunner::*addInGamePassword)();
140  void (GameClientRunner::*addIwad)();
141  void (GameClientRunner::*addModFiles)(const QStringList &);
142  void (GameClientRunner::*addPassword)();
143  void (GameClientRunner::*createCommandLineArguments)();
144 };
145 
146 DPointered(GameClientRunner)
147 
148 POLYMORPHIC_DEFINE(void, GameClientRunner, addConnectCommand, (), ())
149 POLYMORPHIC_DEFINE(void, GameClientRunner, addExtra, (), ())
150 POLYMORPHIC_DEFINE(void, GameClientRunner, addGamePaths, (), ())
151 POLYMORPHIC_DEFINE(void, GameClientRunner, addInGamePassword, (), ())
152 POLYMORPHIC_DEFINE(void, GameClientRunner, addIwad, (), ())
153 POLYMORPHIC_DEFINE(void, GameClientRunner, addModFiles, (const QStringList &files), (files))
154 POLYMORPHIC_DEFINE(void, GameClientRunner, addPassword, (), ())
155 POLYMORPHIC_DEFINE(void, GameClientRunner, createCommandLineArguments, (), ())
156 
157 GameClientRunner::GameClientRunner(ServerPtr server)
158 {
159  set_addConnectCommand(&GameClientRunner::addConnectCommand_default);
160  set_addGamePaths(&GameClientRunner::addGamePaths_default);
161  set_addExtra(&GameClientRunner::addExtra_default);
162  set_addInGamePassword(&GameClientRunner::addInGamePassword_default);
163  set_addIwad(&GameClientRunner::addIwad_default);
164  set_addModFiles(&GameClientRunner::addModFiles_default);
165  set_addPassword(&GameClientRunner::addPassword_default);
166  set_createCommandLineArguments(&GameClientRunner::createCommandLineArguments_default);
167  d->argBexLoading = "-deh";
168  d->argConnect = "-connect";
169  d->argDehLoading = "-deh";
170  d->argIwadLoading = "-iwad";
171  d->argOptionalWadLoading = "-file"; // Assume one does not have this feature.
172  d->argPort = "-port";
173  d->argPwadLoading = "-file";
174  d->argDemoRecord = "-record";
175  d->cli = nullptr;
176  d->server = server;
177 }
178 
179 GameClientRunner::~GameClientRunner()
180 {
181 }
182 
183 void GameClientRunner::addConnectCommand_default()
184 {
185  QString address = QString("%1:%2").arg(d->server->address().toString()).arg(d->server->port());
186  args() << argForConnect() << address;
187 }
188 
190 {
191  IniSection config = gConfig.iniSectionForPlugin(d->server->plugin());
192  QString customParameters = config["CustomParameters"];
193  CommandLineTokenizer tokenizer;
194  args() << tokenizer.tokenize(customParameters);
195 }
196 
198 {
199  args() << argForDemoRecord() << demoName();
200 }
201 
202 void GameClientRunner::addGamePaths_default()
203 {
204  GamePaths paths = gamePaths();
205  if (!paths.isValid())
206  {
207  // gamePaths() sets JoinError.
208  return;
209  }
210 
211  QDir applicationDir = paths.workingDir;
212  if (paths.workingDir.isEmpty())
213  {
214  d->joinError.setType(JoinError::ConfigurationError);
215  d->joinError.setError(tr("Path to working directory for game \"%1\" is empty.\n\n"
216  "Make sure the configuration for the client executable is set properly.")
217  .arg(pluginName()));
218  return;
219  }
220  else if (!applicationDir.exists())
221  {
222  d->joinError.setType(JoinError::ConfigurationError);
223  d->joinError.setError(tr("%1\n\nThis directory cannot be used as working "
224  "directory for game: %2\n\nExecutable: %3")
225  .arg(paths.workingDir, pluginName(), paths.clientExe));
226  return;
227  }
228 
229  setExecutable(paths.clientExe);
230  setWorkingDir(applicationDir.path());
231 }
232 
233 void GameClientRunner::addInGamePassword_default()
234 {
235  if (!argForInGamePassword().isNull())
236  {
237  args() << argForInGamePassword() << inGamePassword();
238  }
239  else
240  {
241  gLog << tr("BUG: Plugin doesn't specify argument for in-game "
242  "password, but the server requires such password.");
243  }
244 }
245 
246 void GameClientRunner::addExtra_default()
247 {
248 }
249 
250 void GameClientRunner::addIwad_default()
251 {
252  args() << argForIwadLoading() << iwadPath();
253 }
254 
256 {
257  addIwad();
258  addPwads();
259 
260  if (!d->incompatiblePwads.isEmpty())
261  {
262  d->joinError.setIncompatibleWads(d->incompatiblePwads);
263  d->joinError.setType(JoinError::MissingWads);
264  }
265  if (!isIwadFound() || !d->missingPwads.isEmpty())
266  {
267  if (!isIwadFound())
268  {
269  d->joinError.setMissingIwad(d->server->iwad());
270  d->joinError.setType(JoinError::MissingWads);
271  }
272  d->joinError.setMissingWads(d->missingPwads);
273  for (const PWad &wad : d->missingPwads)
274  {
275  // Only error if there are required missing wads
276  if (!wad.isOptional())
277  {
278  d->joinError.setType(JoinError::MissingWads);
279  break;
280  }
281  }
282  }
283 }
284 
285 void GameClientRunner::addPassword_default()
286 {
287  if (!argForConnectPassword().isNull())
288  {
289  args() << argForConnectPassword() << connectPassword();
290  }
291  else
292  {
293  gLog << tr("BUG: Plugin doesn't specify argument for connect "
294  "password, but the server is passworded.");
295  }
296 }
297 
299 {
300  auto checkWadsDlg = new CheckWadsDlg(&d->pathFinder);
301  checkWadsDlg->addWads(d->server->wads());
302  const CheckResult checkResults = checkWadsDlg->checkWads();
303  for (const PWad &wad : checkResults.missingWads)
304  {
305  markPwadAsMissing(wad);
306  }
307  for (const PWad &wad : checkResults.incompatibleWads)
308  {
309  markPwadAsIncompatible(wad);
310  }
311  QStringList paths;
312  for (const PWad &wad : checkResults.foundWads)
313  {
314  paths << findWad(wad.name());
315  }
316  addModFiles(paths);
317 }
318 
319 void GameClientRunner::addModFiles_default(const QStringList &files)
320 {
321  for (const QString &file : files)
322  {
323  args() << fileLoadingPrefix(file) << file;
324  }
325 }
326 
327 void GameClientRunner::addModFiles_prefixOnce(const QStringList &files)
328 {
329  QMap<QString, QStringList> groups;
330  for (const QString &file : files)
331  {
332  QString prefix = fileLoadingPrefix(file);
333  groups[prefix] << file;
334  }
335  for (const QString &prefix : groups.keys())
336  {
337  args() << prefix;
338  for (const QString &file : groups[prefix])
339  {
340  args() << file;
341  }
342  }
343 }
344 
345 QString GameClientRunner::fileLoadingPrefix(const QString &file) const
346 {
347  if (file.toLower().endsWith(".deh"))
348  {
349  return argForDehLoading();
350  }
351  else if (file.toLower().endsWith(".bex"))
352  {
353  return argForBexLoading();
354  }
355  return argForPwadLoading();
356 }
357 
359 {
360  return d->cli->args;
361 }
362 
363 const QString &GameClientRunner::argForBexLoading() const
364 {
365  return d->argBexLoading;
366 }
367 
368 const QString &GameClientRunner::argForConnect() const
369 {
370  return d->argConnect;
371 }
372 
374 {
375  return d->argConnectPassword;
376 }
377 
379 {
380  return d->argDehLoading;
381 }
382 
384 {
385  return d->argInGamePassword;
386 }
387 
389 {
390  return d->argIwadLoading;
391 }
392 
394 {
395  return d->argOptionalWadLoading;
396 }
397 
398 const QString &GameClientRunner::argForPort() const
399 {
400  return d->argPort;
401 }
402 
404 {
405  return d->argPwadLoading;
406 }
407 
409 {
410  return d->argDemoRecord;
411 }
412 
413 bool GameClientRunner::canDownloadWadsInGame() const
414 {
415  return d->server->plugin()->data()->inGameFileDownloads;
416 }
417 
418 const QString &GameClientRunner::connectPassword() const
419 {
420  return d->connectParams.connectPassword();
421 }
422 
423 void GameClientRunner::createCommandLineArguments_default()
424 {
425  BAIL_ON_ERROR(addGamePaths());
426  BAIL_ON_ERROR(addConnectCommand());
427  if (d->server->isLocked())
428  {
429  BAIL_ON_ERROR(addPassword());
430  }
431  if (d->server->isLockedInGame())
432  {
433  BAIL_ON_ERROR(addInGamePassword());
434  }
435  if (!demoName().isEmpty())
436  {
437  BAIL_ON_ERROR(addDemoRecordCommand());
438  }
439  BAIL_ON_ERROR(addWads());
440  BAIL_ON_ERROR(addExtra());
441  BAIL_ON_ERROR(addCustomParameters());
442 }
443 
445  const ServerConnectParams &params)
446 {
447  d->cli = &cli;
448  d->cli->args.clear();
449  d->connectParams = params;
450  d->joinError = JoinError();
451 
452  setupPathFinder();
453  if (d->joinError.isError())
454  {
455  return d->joinError;
456  }
457  createCommandLineArguments();
458 
459  return d->joinError;
460 }
461 
462 const QString &GameClientRunner::demoName() const
463 {
464  return d->connectParams.demoName();
465 }
466 
467 QString GameClientRunner::findIwad() const
468 {
469  return findWad(d->server->iwad().toLower());
470 }
471 
472 QString GameClientRunner::findWad(const QString &wad) const
473 {
474  return WadPathFinder(d->pathFinder).find(wad).path();
475 }
476 
477 GameClientRunner::GamePaths GameClientRunner::gamePaths()
478 {
479  Message msg;
480  GamePaths result;
481 
482  QScopedPointer<ExeFile> exeFile(d->server->clientExe());
483  result.clientExe = exeFile->pathToExe(msg);
484  if (result.clientExe.isEmpty())
485  {
487  {
488  d->joinError.setType(JoinError::CanAutomaticallyInstallGame);
489  if (msg.contents().isEmpty())
490  {
491  d->joinError.setError(msg.contents());
492  }
493  else
494  {
495  d->joinError.setError(tr("Game can be installed by Doomseeker"));
496  }
497  }
498  else
499  {
500  d->joinError.setType(JoinError::ConfigurationError);
501  QString error = tr("Client binary cannot be obtained for %1, please "
502  "check the location given in the configuration.").arg(pluginName());
503  if (!msg.isIgnore())
504  {
505  error += "\n\n" + msg.contents();
506  }
507  d->joinError.setError(error);
508  }
509  return GamePaths();
510  }
511  result.workingDir = exeFile->workingDirectory(msg);
512  return result;
513 }
514 
515 const QString &GameClientRunner::inGamePassword() const
516 {
517  return d->connectParams.inGamePassword();
518 }
519 
520 bool GameClientRunner::isFatalError() const
521 {
522  if (d->joinError.isError())
523  {
524  if (d->joinError.isMissingWadsError() && canDownloadWadsInGame())
525  {
526  return false;
527  }
528  return true;
529  }
530  return false;
531 }
532 
533 bool GameClientRunner::isIwadFound() const
534 {
535  return !d->cachedIwadPath.isEmpty();
536 }
537 
538 const QString &GameClientRunner::iwadPath() const
539 {
540  if (!isIwadFound())
541  {
542  d->cachedIwadPath = findIwad();
543  }
544  return d->cachedIwadPath;
545 }
546 
548 {
549  d->missingPwads << pwadName;
550 }
551 
553 {
554  d->incompatiblePwads << pwadName;
555 }
556 
558 {
559  return d->pathFinder;
560 }
561 
562 const QString &GameClientRunner::pluginName() const
563 {
564  return d->server->plugin()->data()->name;
565 }
566 
568 {
569  return d->connectParams;
570 }
571 
572 void GameClientRunner::setArgForBexLoading(const QString &arg)
573 {
574  d->argBexLoading = arg;
575 }
576 
577 void GameClientRunner::setArgForConnect(const QString &arg)
578 {
579  d->argConnect = arg;
580 }
581 
582 void GameClientRunner::setArgForConnectPassword(const QString &arg)
583 {
584  d->argConnectPassword = arg;
585 }
586 
587 void GameClientRunner::setArgForDehLoading(const QString &arg)
588 {
589  d->argDehLoading = arg;
590 }
591 
592 void GameClientRunner::setArgForInGamePassword(const QString &arg)
593 {
594  d->argInGamePassword = arg;
595 }
596 
597 void GameClientRunner::setArgForIwadLoading(const QString &arg)
598 {
599  d->argIwadLoading = arg;
600 }
601 
602 void GameClientRunner::setArgForOptionalWadLoading(const QString &arg)
603 {
604  d->argOptionalWadLoading = arg;
605 }
606 
607 void GameClientRunner::setArgForPort(const QString &arg)
608 {
609  d->argPort = arg;
610 }
611 
612 void GameClientRunner::setArgForPwadLoading(const QString &arg)
613 {
614  d->argPwadLoading = arg;
615 }
616 
617 void GameClientRunner::setArgForDemoRecord(const QString &arg)
618 {
619  d->argDemoRecord = arg;
620 }
621 
622 void GameClientRunner::setExecutable(const QString &path)
623 {
624  d->cli->executable = path;
625 }
626 
627 void GameClientRunner::setWorkingDir(const QString &path)
628 {
629  d->cli->applicationDir.setPath(path);
630 }
631 
633 {
634  return d->joinError;
635 }
636 
638 {
639  d->joinError = e;
640 }
641 
642 void GameClientRunner::setupPathFinder()
643 {
644  d->pathFinder = d->server->wadPathFinder();
645 }
646 
648 {
649  return DoomseekerConfig::config().wadseeker.targetDirectory;
650 }
Manages the checking process of wads when trying to enter a server or when executing the "Find missin...
Definition: checkwadsdlg.h:50
const QString & argForIwadLoading() const
Command line parameter that is used to set IWAD.
const QString & inGamePassword() const
"Join" password required in game.
const QString & argForOptionalWadLoading() const
Command line parameter that is used to load optional WADs.
Performs a case-insensitive (OS independent) file searches.
Definition: pathfinder.h:81
PWAD hosted on a server.
const QString & argForDehLoading() const
Command line parameter that is used to load a DEHACKED file.
const QString & argForConnect() const
Command line parameter that specifies the target server&#39;s IP and port.
Structure holding parameters for application launch.
Definition: apprunner.h:37
Message object used to pass messages throughout the Doomseeker&#39;s system.
Definition: message.h:63
void addCustomParameters()
Adds custom parameters defined by user in configuration box to the args list.
ServerConnectParams & serverConnectParams()
Direct access to ServerConnectParams associated with current command line generation.
void addDemoRecordCommand()
Adds command for demo recording.
A DTO for GameClientRunner; exchanges information between main program and plugins, and allows future extensions.
const QString & demoName() const
Name of the demo if demo should be recorded, otherwise empty.
QString wadTargetDirectory() const
Directory where Doomseeker stores downloaded WADs.
QStringList & args()
Output command line arguments.
Contains the results of CheckWadsDlg::CheckWads(), categorized in "incomplete", "missing" and "found"...
Definition: checkwadsdlg.h:37
void addPwads()
Finds and adds each game modification file to the args list, marks missing files. ...
JoinError joinError() const
JoinError set by last call to createJoinCommandLine().
static const unsigned GAME_NOT_FOUND_BUT_CAN_BE_INSTALLED
Indicates that program executable was not found, but Doomseeker or plugin are capable of performing t...
Definition: message.h:117
const QString & argForConnectPassword() const
Command line parameter that is used to specify connection password.
Indicator of error for the server join process.
Definition: joinerror.h:41
const QString & argForPort() const
Command line parameter that is used to set internet port for the game.
void markPwadAsIncompatible(const PWad &pwadName)
Stores PWAD in an internal list of incompatible WADs.
JoinError createJoinCommandLine(CommandLineInfo &cli, const ServerConnectParams &params)
Fills out CommandLineInfo object that allows client executables to be launched.
bool isIgnore() const
True for &#39;Null&#39; messages.
Definition: message.cpp:109
PathFinder & pathFinder()
Reference to a PathFinder belonging to this GameClientRunner.
QString findWad(const QString &wad) const
Finds WAD in a way that supports user configured aliases.
const QString & argForPwadLoading() const
Command line parameter that is used to load a PWAD.
const QString & connectPassword() const
Password for server connection.
void addModFiles_prefixOnce(const QStringList &files)
addModFiles() implementation that sorts files by extension and uses loading prefix arg only once for ...
void addWads()
Calls addIwad() then addPwads(), sets JoinError::MissingWads in case of failure.
static DoomseekerConfig & config()
Returns the Singleton.
const QString & argForDemoRecord() const
Command line parameter for recording a demo.
void setJoinError(const JoinError &e)
Apply error that is passed to the launching routine and can be displayed to user. ...
const QString & argForInGamePassword() const
Command line parameter that is used to specify in-game ("join") password.
INI section representation.
Definition: inisection.h:40
const QString & demoName() const
Name of demo if demo is to be recorded.
Splits command line into separate arguments in a manner appropriate for current OS.
Game executable was not found but it can be automatically installed by the plugin.
Definition: joinerror.h:60
bool isOptional() const
Is this WAD required to join the server?
const QString & inGamePassword() const
In-game "join" password.
Wrapper for PathFinder that specializes in findings WADs.
Definition: wadpathfinder.h:76
unsigned type() const
Message::Type.
Definition: message.cpp:124
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
Creates command line that launches the client executable of the game and connects it to a server...
void markPwadAsMissing(const PWad &pwadName)
Stores PWAD in an internal list of missing WADs.
const QString & connectPassword() const
Password for server connection.
const QString & name() const
File name of the WAD.