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::*addInGamePassword)();
137  void (GameClientRunner::*addIwad)();
138  void (GameClientRunner::*addPassword)();
139  void (GameClientRunner::*createCommandLineArguments)();
140 };
141 
142 DPointered(GameClientRunner)
143 
144 POLYMORPHIC_DEFINE(void, GameClientRunner, addConnectCommand, (), ());
145 POLYMORPHIC_DEFINE(void, GameClientRunner, addExtra, (), ());
146 POLYMORPHIC_DEFINE(void, GameClientRunner, addInGamePassword, (), ());
147 POLYMORPHIC_DEFINE(void, GameClientRunner, addIwad, (), ());
148 POLYMORPHIC_DEFINE(void, GameClientRunner, addPassword, (), ());
149 POLYMORPHIC_DEFINE(void, GameClientRunner, createCommandLineArguments, (), ());
150 
151 GameClientRunner::GameClientRunner(ServerPtr server)
152 {
153  set_addConnectCommand(&GameClientRunner::addConnectCommand_default);
154  set_addExtra(&GameClientRunner::addExtra_default);
155  set_addInGamePassword(&GameClientRunner::addInGamePassword_default);
156  set_addIwad(&GameClientRunner::addIwad_default);
157  set_addPassword(&GameClientRunner::addPassword_default);
158  set_createCommandLineArguments(&GameClientRunner::createCommandLineArguments_default);
159  d->argBexLoading = "-deh";
160  d->argConnect = "-connect";
161  d->argDehLoading = "-deh";
162  d->argIwadLoading = "-iwad";
163  d->argOptionalWadLoading = "-file"; // Assume one does not have this feature.
164  d->argPort = "-port";
165  d->argPwadLoading = "-file";
166  d->argDemoRecord = "-record";
167  d->cli = NULL;
168  d->server = server;
169 }
170 
171 GameClientRunner::~GameClientRunner()
172 {
173 }
174 
175 void GameClientRunner::addConnectCommand_default()
176 {
177  QString address = QString("%1:%2").arg(d->server->address().toString()).arg(d->server->port());
178  args() << argForConnect() << address;
179 }
180 
182 {
183  IniSection config = gConfig.iniSectionForPlugin(d->server->plugin());
184  QString customParameters = config["CustomParameters"];
185  CommandLineTokenizer tokenizer;
186  args() << tokenizer.tokenize(customParameters);
187 }
188 
190 {
191  args() << argForDemoRecord() << demoName();
192 }
193 
195 {
196  GamePaths paths = gamePaths();
197  if (!paths.isValid())
198  {
199  // gamePaths() sets JoinError.
200  return;
201  }
202 
203  QDir applicationDir = paths.workingDir;
204  if (paths.workingDir.isEmpty())
205  {
206  d->joinError.setType(JoinError::ConfigurationError);
207  d->joinError.setError(tr("Path to working directory for game \"%1\" is empty.\n\n"
208  "Make sure the configuration for the client executable is set properly.")
209  .arg(pluginName()));
210  return;
211  }
212  else if (!applicationDir.exists())
213  {
214  d->joinError.setType(JoinError::ConfigurationError);
215  d->joinError.setError(tr("%1\n\nThis directory cannot be used as working "
216  "directory for game: %2\n\nExecutable: %3")
217  .arg(paths.workingDir, pluginName(), paths.clientExe));
218  return;
219  }
220 
221  d->cli->executable = paths.clientExe;
222  d->cli->applicationDir = applicationDir;
223 }
224 
225 void GameClientRunner::addInGamePassword_default()
226 {
227  if (!argForInGamePassword().isNull())
228  {
230  }
231  else
232  {
233  gLog << tr("BUG: Plugin doesn't specify argument for in-game "
234  "password, but the server requires such password.");
235  }
236 }
237 
238 void GameClientRunner::addExtra_default()
239 {
240 }
241 
242 void GameClientRunner::addIwad_default()
243 {
244  args() << argForIwadLoading() << iwadPath();
245 }
246 
248 {
249  addIwad();
250  addPwads();
251 
252  if (!isIwadFound() || !d->missingPwads.isEmpty())
253  {
254  if (!isIwadFound())
255  {
256  d->joinError.setMissingIwad(d->server->iwad());
257  }
258  d->joinError.setMissingWads(d->missingPwads);
259  foreach(const PWad &wad, d->missingPwads)
260  {
261  // Only error if there are required missing wads
262  if(!wad.isOptional())
263  {
264  d->joinError.setType(JoinError::MissingWads);
265  break;
266  }
267  }
268  }
269 }
270 
271 void GameClientRunner::addPassword_default()
272 {
273  if (!argForConnectPassword().isNull())
274  {
276  }
277  else
278  {
279  gLog << tr("BUG: Plugin doesn't specify argument for connect "
280  "password, but the server is passworded.");
281  }
282 }
283 
285 {
286  for (int i = 0; i < d->server->numWads(); ++i)
287  {
288  QString pwad = findWad(d->server->wad(i).name());
289  if (pwad.isEmpty())
290  {
291  markPwadAsMissing(d->server->wad(i));
292  }
293  else
294  {
295  if (pwad.toLower().endsWith(".deh"))
296  {
297  args() << argForDehLoading() << pwad;
298  }
299  else if (pwad.toLower().endsWith(".bex"))
300  {
301  args() << argForBexLoading() << pwad;
302  }
303  else
304  {
305  args() << argForPwadLoading() << pwad;
306  }
307  }
308  }
309 }
310 
312 {
313  return d->cli->args;
314 }
315 
316 const QString& GameClientRunner::argForBexLoading() const
317 {
318  return d->argBexLoading;
319 }
320 
321 const QString& GameClientRunner::argForConnect() const
322 {
323  return d->argConnect;
324 }
325 
327 {
328  return d->argConnectPassword;
329 }
330 
332 {
333  return d->argDehLoading;
334 }
335 
337 {
338  return d->argInGamePassword;
339 }
340 
342 {
343  return d->argIwadLoading;
344 }
345 
347 {
348  return d->argOptionalWadLoading;
349 }
350 
351 const QString& GameClientRunner::argForPort() const
352 {
353  return d->argPort;
354 }
355 
357 {
358  return d->argPwadLoading;
359 }
360 
362 {
363  return d->argDemoRecord;
364 }
365 
366 bool GameClientRunner::canDownloadWadsInGame() const
367 {
368  return d->server->plugin()->data()->inGameFileDownloads;
369 }
370 
371 const QString& GameClientRunner::connectPassword() const
372 {
373  return d->connectParams.connectPassword();
374 }
375 
376 void GameClientRunner::createCommandLineArguments_default()
377 {
378  BAIL_ON_ERROR(addGamePaths());
379  BAIL_ON_ERROR(addConnectCommand());
380  if (d->server->isLocked())
381  {
382  BAIL_ON_ERROR(addPassword());
383  }
384  if (d->server->isLockedInGame())
385  {
386  BAIL_ON_ERROR(addInGamePassword());
387  }
388  if (!demoName().isEmpty())
389  {
390  BAIL_ON_ERROR(addDemoRecordCommand());
391  }
392  BAIL_ON_ERROR(addWads());
393  BAIL_ON_ERROR(addExtra());
394  BAIL_ON_ERROR(addCustomParameters());
395 }
396 
398  const ServerConnectParams& params)
399 {
400  d->cli = &cli;
401  d->cli->args.clear();
402  d->connectParams = params;
403  d->joinError = JoinError();
404 
405  setupPathFinder();
406  if (d->joinError.isError())
407  {
408  return d->joinError;
409  }
411 
412  return d->joinError;
413 }
414 
415 const QString& GameClientRunner::demoName() const
416 {
417  return d->connectParams.demoName();
418 }
419 
420 QString GameClientRunner::findIwad() const
421 {
422  return findWad(d->server->iwad().toLower());
423 }
424 
425 QString GameClientRunner::findWad(const QString &wad) const
426 {
427  return WadPathFinder(d->pathFinder).find(wad).path();
428 }
429 
430 GameClientRunner::GamePaths GameClientRunner::gamePaths()
431 {
432  Message msg;
433  GamePaths result;
434 
435  QScopedPointer<ExeFile> exeFile(d->server->clientExe());
436  result.clientExe = exeFile->pathToExe(msg);
437  if (result.clientExe.isEmpty())
438  {
440  {
441  d->joinError.setType(JoinError::CanAutomaticallyInstallGame);
442  if (msg.contents().isEmpty())
443  {
444  d->joinError.setError(msg.contents());
445  }
446  else
447  {
448  d->joinError.setError(tr("Game can be installed by Doomseeker"));
449  }
450  }
451  else
452  {
453  d->joinError.setType(JoinError::ConfigurationError);
454  QString error = tr("Client binary cannot be obtained for %1, please "
455  "check the location given in the configuration.").arg(pluginName());
456  if (!msg.isIgnore())
457  {
458  error += "\n\n" + msg.contents();
459  }
460  d->joinError.setError(error);
461  }
462  return GamePaths();
463  }
464  result.workingDir = exeFile->workingDirectory(msg);
465 
466  GameExeRetriever exeRetriever = GameExeRetriever(*d->server->plugin()->gameExe());
467  result.offlineExe = pathToOfflineExe(msg);
468 
469  return result;
470 }
471 
472 const QString& GameClientRunner::inGamePassword() const
473 {
474  return d->connectParams.inGamePassword();
475 }
476 
477 bool GameClientRunner::isFatalError() const
478 {
479  if (d->joinError.isError())
480  {
481  if (d->joinError.isMissingWadsError() && canDownloadWadsInGame())
482  {
483  return false;
484  }
485  return true;
486  }
487  return false;
488 }
489 
490 bool GameClientRunner::isIwadFound() const
491 {
492  return !d->cachedIwadPath.isEmpty();
493 }
494 
495 const QString& GameClientRunner::iwadPath() const
496 {
497  if (!isIwadFound())
498  {
499  d->cachedIwadPath = findIwad();
500  }
501  return d->cachedIwadPath;
502 }
503 
505 {
506  d->missingPwads << pwadName;
507 }
508 
510 {
511  return d->pathFinder;
512 }
513 
515 {
516  GameExeRetriever exeRetriever = GameExeRetriever(*d->server->plugin()->gameExe());
517  return exeRetriever.pathToOfflineExe(msg);
518 }
519 
520 const QString& GameClientRunner::pluginName() const
521 {
522  return d->server->plugin()->data()->name;
523 }
524 
526 {
527  return d->connectParams;
528 }
529 
530 void GameClientRunner::setArgForBexLoading(const QString& arg)
531 {
532  d->argBexLoading = arg;
533 }
534 
535 void GameClientRunner::setArgForConnect(const QString& arg)
536 {
537  d->argConnect = arg;
538 }
539 
540 void GameClientRunner::setArgForConnectPassword(const QString& arg)
541 {
542  d->argConnectPassword = arg;
543 }
544 
545 void GameClientRunner::setArgForDehLoading(const QString& arg)
546 {
547  d->argDehLoading = arg;
548 }
549 
550 void GameClientRunner::setArgForInGamePassword(const QString& arg)
551 {
552  d->argInGamePassword = arg;
553 }
554 
555 void GameClientRunner::setArgForIwadLoading(const QString& arg)
556 {
557  d->argIwadLoading = arg;
558 }
559 
560 void GameClientRunner::setArgForOptionalWadLoading(const QString& arg)
561 {
562  d->argOptionalWadLoading = arg;
563 }
564 
565 void GameClientRunner::setArgForPort(const QString& arg)
566 {
567  d->argPort = arg;
568 }
569 
570 void GameClientRunner::setArgForPwadLoading(const QString& arg)
571 {
572  d->argPwadLoading = arg;
573 }
574 
575 void GameClientRunner::setArgForDemoRecord(const QString& arg)
576 {
577  d->argDemoRecord = arg;
578 }
579 
581 {
582  return d->joinError;
583 }
584 
586 {
587  d->joinError = e;
588 }
589 
590 void GameClientRunner::setupPathFinder()
591 {
592  d->pathFinder = d->server->wadPathFinder();
593 }
594 
596 {
597  return DoomseekerConfig::config().wadseeker.targetDirectory;
598 }
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's IP and port.
Structure holding parameters for application launch.
Definition: apprunner.h:37
Message object used to pass messages throughout the Doomseeker's system.
Definition: message.h:62
A convenience wrapper class for GameExeFactory.
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 PWAD to the args list, marks missing WADs.
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:116
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 'Null' 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 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.
void addConnectCommand()
[Virtual] Adds connection arguments to the list.
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
void addGamePaths()
Sets working directory and path to executable in out put CommandLineInfo.
bool isOptional() const
Is this WAD required to join the server?
const QString & inGamePassword() const
In-game "join" password.
void addPassword()
[Virtual] Adds connect password to the args list.
void addIwad()
[Virtual] Plugins can replace IWAD discovery mechanism and generation of relevant executable paramete...
Wrapper for PathFinder that specializes in findings WADs.
Definition: wadpathfinder.h:54
unsigned type() const
Message::Type.
Definition: message.cpp:124
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
void addExtra()
[Virtual] Plugins can easily add plugin-specific arguments here.
Creates command line that launches the client executable of the game and connects it to a server...
QString pathToOfflineExe(Message &msg)
Retrieves path to offline exe from plugin's ExeFile.
void markPwadAsMissing(const PWad &pwadName)
Stores PWAD in an internal list of missing WADs.
void createCommandLineArguments()
[Virtual] Spawns entire command line for client executable launch.
const QString & connectPassword() const
Password for server connection.
void addInGamePassword()
[Virtual] Adds in-game password to the args list.