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 "templatedpathresolver.h"
40 #include <QDir>
41 #include <QFileInfo>
42 #include <QScopedPointer>
43 #include <QStringList>
44 
45 DClass<ServerConnectParams>
46 {
47 public:
48  QString connectPassword;
49  QString demoName;
50  QString inGamePassword;
51 };
52 
53 DPointered(ServerConnectParams)
54 
56 {
57 }
58 
59 ServerConnectParams::ServerConnectParams(const ServerConnectParams &other)
60 {
61  d = other.d;
62 }
63 
64 ServerConnectParams &ServerConnectParams::operator=(const ServerConnectParams &other)
65 {
66  d = other.d;
67  return *this;
68 }
69 
70 ServerConnectParams::~ServerConnectParams()
71 {
72 }
73 
75 {
76  return d->connectPassword;
77 }
78 
79 const QString &ServerConnectParams::demoName() const
80 {
81  return d->demoName;
82 }
83 
85 {
86  return d->inGamePassword;
87 }
88 
89 void ServerConnectParams::setConnectPassword(const QString &val)
90 {
91  d->connectPassword = val;
92 }
93 
94 void ServerConnectParams::setDemoName(const QString &val)
95 {
96  d->demoName = val;
97 }
98 
99 void ServerConnectParams::setInGamePassword(const QString &val)
100 {
101  d->inGamePassword = val;
102 }
104 #define BAIL_ON_ERROR(method) \
105  { \
106  method; \
107  if (isFatalError()) \
108  { \
109  return; \
110  } \
111  }
112 
113 
114 DClass<GameClientRunner>
115 {
116 public:
117  QString argBexLoading;
118  QString argConnect;
119  QString argConnectPassword;
120  QString argDehLoading;
121  QString argInGamePassword;
122  QString argIwadLoading;
123  QString argOptionalWadLoading;
124  QString argPort;
125  QString argPwadLoading;
126  QString argDemoRecord;
127 
128  QStringList args;
129  mutable QString cachedIwadPath;
130  ServerConnectParams connectParams;
131  CommandLineInfo *cli;
132  JoinError joinError;
133  QList<PWad> missingPwads;
134  QList<PWad> incompatiblePwads;
135  PathFinder pathFinder;
136  ServerPtr server;
137 
138  void (GameClientRunner::*addConnectCommand)();
139  void (GameClientRunner::*addExtra)();
140  void (GameClientRunner::*addGamePaths)();
141  void (GameClientRunner::*addInGamePassword)();
142  void (GameClientRunner::*addIwad)();
143  void (GameClientRunner::*addModFiles)(const QStringList &);
144  void (GameClientRunner::*addPassword)();
145  void (GameClientRunner::*createCommandLineArguments)();
146 };
147 
148 DPointered(GameClientRunner)
149 
150 POLYMORPHIC_DEFINE(void, GameClientRunner, addConnectCommand, (), ())
151 POLYMORPHIC_DEFINE(void, GameClientRunner, addExtra, (), ())
152 POLYMORPHIC_DEFINE(void, GameClientRunner, addGamePaths, (), ())
153 POLYMORPHIC_DEFINE(void, GameClientRunner, addInGamePassword, (), ())
154 POLYMORPHIC_DEFINE(void, GameClientRunner, addIwad, (), ())
155 POLYMORPHIC_DEFINE(void, GameClientRunner, addModFiles, (const QStringList &files), (files))
156 POLYMORPHIC_DEFINE(void, GameClientRunner, addPassword, (), ())
157 POLYMORPHIC_DEFINE(void, GameClientRunner, createCommandLineArguments, (), ())
158 
159 GameClientRunner::GameClientRunner(ServerPtr server)
160 {
161  set_addConnectCommand(&GameClientRunner::addConnectCommand_default);
162  set_addGamePaths(&GameClientRunner::addGamePaths_default);
163  set_addExtra(&GameClientRunner::addExtra_default);
164  set_addInGamePassword(&GameClientRunner::addInGamePassword_default);
165  set_addIwad(&GameClientRunner::addIwad_default);
166  set_addModFiles(&GameClientRunner::addModFiles_default);
167  set_addPassword(&GameClientRunner::addPassword_default);
168  set_createCommandLineArguments(&GameClientRunner::createCommandLineArguments_default);
169  d->argBexLoading = "-deh";
170  d->argConnect = "-connect";
171  d->argDehLoading = "-deh";
172  d->argIwadLoading = "-iwad";
173  d->argOptionalWadLoading = "-file"; // Assume one does not have this feature.
174  d->argPort = "-port";
175  d->argPwadLoading = "-file";
176  d->argDemoRecord = "-record";
177  d->cli = nullptr;
178  d->server = server;
179 }
180 
181 GameClientRunner::~GameClientRunner()
182 {
183 }
184 
185 void GameClientRunner::addConnectCommand_default()
186 {
187  QString address = QString("%1:%2").arg(d->server->address().toString()).arg(d->server->port());
188  args() << argForConnect() << address;
189 }
190 
192 {
193  IniSection config = gConfig.iniSectionForPlugin(d->server->plugin());
194  QString customParameters = config["CustomParameters"];
195  CommandLineTokenizer tokenizer;
196  args() << tokenizer.tokenize(customParameters);
197 }
198 
200 {
201  args() << argForDemoRecord() << demoName();
202 }
203 
204 void GameClientRunner::addGamePaths_default()
205 {
206  GamePaths paths = gamePaths();
207  if (!paths.isValid())
208  {
209  // gamePaths() sets JoinError.
210  return;
211  }
212 
213  QDir applicationDir = paths.workingDir;
214  if (paths.workingDir.isEmpty())
215  {
216  d->joinError.setType(JoinError::ConfigurationError);
217  d->joinError.setError(tr("Cannot determine the working directory for \"%1\". "
218  "Check the game configuration.")
219  .arg(pluginName()));
220  return;
221  }
222  else if (!applicationDir.exists())
223  {
224  d->joinError.setType(JoinError::ConfigurationError);
225  d->joinError.setError(tr("This path cannot be used as the working directory for %1:\n%2.")
226  .arg(pluginName(), paths.workingDir));
227  return;
228  }
229 
230  setExecutable(paths.clientExe);
231  setWorkingDir(applicationDir.path());
232 }
233 
234 void GameClientRunner::addInGamePassword_default()
235 {
236  if (!argForInGamePassword().isNull())
237  {
239  }
240  else
241  {
242  gLog << tr("BUG: Plugin doesn't specify argument for in-game "
243  "password, but the server requires such password.");
244  }
245 }
246 
247 void GameClientRunner::addExtra_default()
248 {
249 }
250 
251 void GameClientRunner::addIwad_default()
252 {
253  args() << argForIwadLoading() << QFileInfo(iwadPath()).absoluteFilePath();
254 }
255 
257 {
258  addIwad();
259  addPwads();
260 
261  if (!d->incompatiblePwads.isEmpty())
262  {
263  d->joinError.setIncompatibleWads(d->incompatiblePwads);
264  d->joinError.setType(JoinError::MissingWads);
265  }
266  if (!isIwadFound() || !d->missingPwads.isEmpty())
267  {
268  if (!isIwadFound())
269  {
270  d->joinError.setMissingIwad(d->server->iwad());
271  d->joinError.setType(JoinError::MissingWads);
272  }
273  d->joinError.setMissingWads(d->missingPwads);
274  for (const PWad &wad : d->missingPwads)
275  {
276  // Only error if there are required missing wads
277  if (!wad.isOptional())
278  {
279  d->joinError.setType(JoinError::MissingWads);
280  break;
281  }
282  }
283  }
284 }
285 
286 void GameClientRunner::addPassword_default()
287 {
288  if (!argForConnectPassword().isNull())
289  {
291  }
292  else
293  {
294  gLog << tr("BUG: Plugin doesn't specify argument for connect "
295  "password, but the server is passworded.");
296  }
297 }
298 
300 {
301  auto checkWadsDlg = new CheckWadsDlg(&d->pathFinder);
302  checkWadsDlg->addWads(d->server->wads());
303  const CheckResult checkResults = checkWadsDlg->checkWads();
304  for (const PWad &wad : checkResults.missingWads)
305  {
306  markPwadAsMissing(wad);
307  }
308  for (const PWad &wad : checkResults.incompatibleWads)
309  {
311  }
312  QStringList paths;
313  for (const PWad &wad : checkResults.foundWads)
314  {
315  paths << findWad(wad.name());
316  }
317  addModFiles(paths);
318 }
319 
320 void GameClientRunner::addModFiles_default(const QStringList &files)
321 {
322  for (const QString &file : files)
323  {
324  args() << fileLoadingPrefix(file) << QFileInfo(file).absoluteFilePath();
325  }
326 }
327 
328 void GameClientRunner::addModFiles_prefixOnce(const QStringList &files)
329 {
330  QMap<QString, QStringList> groups;
331  for (const QString &file : files)
332  {
333  QString prefix = fileLoadingPrefix(file);
334  groups[prefix] << file;
335  }
336  for (const QString &prefix : groups.keys())
337  {
338  args() << prefix;
339  for (const QString &file : groups[prefix])
340  {
341  args() << file;
342  }
343  }
344 }
345 
346 QString GameClientRunner::fileLoadingPrefix(const QString &file) const
347 {
348  if (file.toLower().endsWith(".deh"))
349  {
350  return argForDehLoading();
351  }
352  else if (file.toLower().endsWith(".bex"))
353  {
354  return argForBexLoading();
355  }
356  return argForPwadLoading();
357 }
358 
360 {
361  return d->cli->args;
362 }
363 
364 const QString &GameClientRunner::argForBexLoading() const
365 {
366  return d->argBexLoading;
367 }
368 
369 const QString &GameClientRunner::argForConnect() const
370 {
371  return d->argConnect;
372 }
373 
375 {
376  return d->argConnectPassword;
377 }
378 
380 {
381  return d->argDehLoading;
382 }
383 
385 {
386  return d->argInGamePassword;
387 }
388 
390 {
391  return d->argIwadLoading;
392 }
393 
395 {
396  return d->argOptionalWadLoading;
397 }
398 
399 const QString &GameClientRunner::argForPort() const
400 {
401  return d->argPort;
402 }
403 
405 {
406  return d->argPwadLoading;
407 }
408 
410 {
411  return d->argDemoRecord;
412 }
413 
414 bool GameClientRunner::canDownloadWadsInGame() const
415 {
416  return d->server->plugin()->data()->inGameFileDownloads;
417 }
418 
419 const QString &GameClientRunner::connectPassword() const
420 {
421  return d->connectParams.connectPassword();
422 }
423 
424 void GameClientRunner::createCommandLineArguments_default()
425 {
426  BAIL_ON_ERROR(addGamePaths());
427  BAIL_ON_ERROR(addConnectCommand());
428  if (d->server->isLocked())
429  {
430  BAIL_ON_ERROR(addPassword());
431  }
432  if (d->server->isLockedInGame())
433  {
434  BAIL_ON_ERROR(addInGamePassword());
435  }
436  if (!demoName().isEmpty())
437  {
438  BAIL_ON_ERROR(addDemoRecordCommand());
439  }
440  BAIL_ON_ERROR(addWads());
441  BAIL_ON_ERROR(addExtra());
442  BAIL_ON_ERROR(addCustomParameters());
443 }
444 
446  const ServerConnectParams &params)
447 {
448  d->cli = &cli;
449  d->cli->args.clear();
450  d->connectParams = params;
451  d->joinError = JoinError();
452 
453  setupPathFinder();
454  if (d->joinError.isError())
455  {
456  return d->joinError;
457  }
459 
460  return d->joinError;
461 }
462 
463 const QString &GameClientRunner::demoName() const
464 {
465  return d->connectParams.demoName();
466 }
467 
468 QString GameClientRunner::findIwad() const
469 {
470  return findWad(d->server->iwad().toLower());
471 }
472 
473 QString GameClientRunner::findWad(const QString &wad) const
474 {
475  return WadPathFinder(d->pathFinder).find(wad).path();
476 }
477 
478 GameClientRunner::GamePaths GameClientRunner::gamePaths()
479 {
480  Message msg;
481  GamePaths result;
482 
483  QScopedPointer<ExeFile> exeFile(d->server->clientExe());
484  result.clientExe = exeFile->pathToExe(msg);
485  if (result.clientExe.isEmpty())
486  {
488  {
489  d->joinError.setType(JoinError::CanAutomaticallyInstallGame);
490  if (msg.contents().isEmpty())
491  {
492  d->joinError.setError(msg.contents());
493  }
494  else
495  {
496  d->joinError.setError(tr("The game can be installed by Doomseeker."));
497  }
498  }
499  else
500  {
501  d->joinError.setType(JoinError::ConfigurationError);
502  if (!msg.isIgnore())
503  {
504  d->joinError.setError(msg.contents());
505  }
506  else
507  {
508  d->joinError.setError(tr("Couldn't find game %1."));
509  }
510  }
511  return GamePaths();
512  }
513  result.workingDir = exeFile->workingDirectory(msg);
514  return result;
515 }
516 
517 const QString &GameClientRunner::inGamePassword() const
518 {
519  return d->connectParams.inGamePassword();
520 }
521 
522 bool GameClientRunner::isFatalError() const
523 {
524  if (d->joinError.isError())
525  {
526  if (d->joinError.isMissingWadsError() && canDownloadWadsInGame())
527  {
528  return false;
529  }
530  return true;
531  }
532  return false;
533 }
534 
535 bool GameClientRunner::isIwadFound() const
536 {
537  return !d->cachedIwadPath.isEmpty();
538 }
539 
540 const QString &GameClientRunner::iwadPath() const
541 {
542  if (!isIwadFound())
543  {
544  d->cachedIwadPath = findIwad();
545  }
546  return d->cachedIwadPath;
547 }
548 
550 {
551  d->missingPwads << pwadName;
552 }
553 
555 {
556  d->incompatiblePwads << pwadName;
557 }
558 
560 {
561  return d->pathFinder;
562 }
563 
564 const QString &GameClientRunner::pluginName() const
565 {
566  return d->server->plugin()->data()->name;
567 }
568 
570 {
571  return d->connectParams;
572 }
573 
574 void GameClientRunner::setArgForBexLoading(const QString &arg)
575 {
576  d->argBexLoading = arg;
577 }
578 
579 void GameClientRunner::setArgForConnect(const QString &arg)
580 {
581  d->argConnect = arg;
582 }
583 
584 void GameClientRunner::setArgForConnectPassword(const QString &arg)
585 {
586  d->argConnectPassword = arg;
587 }
588 
589 void GameClientRunner::setArgForDehLoading(const QString &arg)
590 {
591  d->argDehLoading = arg;
592 }
593 
594 void GameClientRunner::setArgForInGamePassword(const QString &arg)
595 {
596  d->argInGamePassword = arg;
597 }
598 
599 void GameClientRunner::setArgForIwadLoading(const QString &arg)
600 {
601  d->argIwadLoading = arg;
602 }
603 
604 void GameClientRunner::setArgForOptionalWadLoading(const QString &arg)
605 {
606  d->argOptionalWadLoading = arg;
607 }
608 
609 void GameClientRunner::setArgForPort(const QString &arg)
610 {
611  d->argPort = arg;
612 }
613 
614 void GameClientRunner::setArgForPwadLoading(const QString &arg)
615 {
616  d->argPwadLoading = arg;
617 }
618 
619 void GameClientRunner::setArgForDemoRecord(const QString &arg)
620 {
621  d->argDemoRecord = arg;
622 }
623 
624 void GameClientRunner::setExecutable(const QString &path)
625 {
626  d->cli->executable = QFileInfo(gDoomseekerTemplatedPathResolver().resolve(path));
627 }
628 
629 void GameClientRunner::setWorkingDir(const QString &path)
630 {
631  d->cli->applicationDir.setPath(gDoomseekerTemplatedPathResolver().resolve(path));
632 }
633 
635 {
636  return d->joinError;
637 }
638 
640 {
641  d->joinError = e;
642 }
643 
644 void GameClientRunner::setupPathFinder()
645 {
646  d->pathFinder = d->server->wadPathFinder();
647 }
648 
650 {
652  DoomseekerConfig::config().wadseeker.targetDirectory);
653 }