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  {
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  {
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  {
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  }
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 }