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