gamehost.cpp
1 //------------------------------------------------------------------------------
2 // gamehost.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) 2013 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "gamehost.h"
24 
25 #include "apprunner.h"
26 #include "commandlinetokenizer.h"
27 #include "configuration/doomseekerconfig.h"
28 #include "gamedemo.h"
29 #include "ini/inisection.h"
30 #include "ini/inivariable.h"
31 #include "plugins/engineplugin.h"
32 #include "serverapi/gamecreateparams.h"
33 #include "serverapi/message.h"
35 #include "templatedpathresolver.h"
36 #include <cassert>
37 #include <QFileInfo>
38 #include <QStringList>
39 
40 #define BAIL_ON_ERROR(method) \
41  { \
42  method; \
43  if (d->message.isError()) \
44  { \
45  return; \
46  } \
47  }
48 
49 DClass<GameHost>
50 {
51 public:
52  QString argBexLoading;
53  QString argDehLoading;
54  QString argIwadLoading;
55  QString argOptionalWadLoading;
56  QString argPort;
57  QString argPwadLoading;
58  QString argDemoPlayback;
59  QString argDemoRecord;
60  QString argServerLaunch;
61 
62  CommandLineInfo *currentCmdLine;
63  Message message;
64  GameCreateParams params;
65  EnginePlugin *plugin;
66 
67  void (GameHost::*addIwad)();
68  void (GameHost::*addPwads)();
69  void (GameHost::*addDMFlags)();
70  void (GameHost::*addGlobalGameCustomParameters)();
71 };
72 
73 DPointered(GameHost)
74 
76 {
77  d->argBexLoading = "-deh";
78  d->argDehLoading = "-deh";
79  d->argIwadLoading = "-iwad";
80  d->argOptionalWadLoading = "-file";
81  d->argPort = "-port";
82  d->argPwadLoading = "-file";
83  d->argDemoPlayback = "-playdemo";
84  d->argDemoRecord = "-record";
85  d->currentCmdLine = nullptr;
86  d->plugin = plugin;
87 
88  set_addIwad(&GameHost::addIwad_default);
89  set_addPwads(&GameHost::addPwads_default);
90  set_addDMFlags(&GameHost::addDMFlags_default);
91  set_addGlobalGameCustomParameters(&GameHost::addGlobalGameCustomParameters_default);
92 }
93 
94 GameHost::~GameHost()
95 {
96 }
97 
98 POLYMORPHIC_DEFINE(void, GameHost, addIwad, (), ())
99 POLYMORPHIC_DEFINE(void, GameHost, addPwads, (), ())
100 POLYMORPHIC_DEFINE(void, GameHost, addDMFlags, (), ())
101 POLYMORPHIC_DEFINE(void, GameHost, addGlobalGameCustomParameters, (), ())
102 
103 void GameHost::addCustomParameters()
104 {
105  args().append(params().customParameters());
106 }
107 
108 void GameHost::addDemoPlaybackIfApplicable()
109 {
110  if (params().hostMode() == GameCreateParams::Demo)
111  {
112  args() << argForDemoPlayback();
114  }
115 }
116 
117 void GameHost::addDemoRecordIfApplicable()
118 {
119  if (params().hostMode() == GameCreateParams::Offline
120  && params().demoRecord() != GameDemo::NoDemo)
121  {
122  args() << argForDemoRecord();
124  }
125 }
126 
127 void GameHost::addDMFlags_default()
128 {
129 }
130 
132 {
133 }
134 
135 void GameHost::addGlobalGameCustomParameters_default()
136 {
137  IniSection config = gConfig.iniSectionForPlugin(plugin());
138  QString customParameters = config["CustomParameters"];
139  CommandLineTokenizer tokenizer;
140  args() << tokenizer.tokenize(customParameters);
141 }
142 
143 void GameHost::addIwad_default()
144 {
145  const QString &iwadPath = params().iwadPath();
146 
147  if (iwadPath.isEmpty())
148  {
149  setMessage(Message::customError(tr("IWAD is not set")));
150  return;
151  }
152 
153  QFileInfo fi(gDoomseekerTemplatedPathResolver().resolve(iwadPath));
154 
155  if (!fi.isFile())
156  {
157  QString error = tr("IWAD Path error:\n\"%1\" doesn't exist or is a directory!").arg(iwadPath);
159  }
160 
161  args() << argForIwadLoading() << fi.absoluteFilePath();
162 }
163 
164 void GameHost::addPwads_default()
165 {
166  verifyPwadPaths();
167  for (int i = 0; i < params().pwadsPaths().size(); ++i)
168  {
169  const QString &pwad = params().pwadsPaths()[i];
170  QFileInfo fileInfo(gDoomseekerTemplatedPathResolver().resolve(pwad));
171  args() << fileLoadingPrefix(i) << fileInfo.absoluteFilePath();
172  }
173 }
174 
176 {
177  verifyPwadPaths();
178  QMap<QString, QStringList> groups;
179  for (int i = 0; i < params().pwadsPaths().size(); ++i)
180  {
181  const QString &pwad = params().pwadsPaths()[i];
182  QString prefix = fileLoadingPrefix(i);
183  groups[prefix] << pwad;
184  }
185  for (const QString &prefix : groups.keys())
186  {
187  args() << prefix;
188  for (const QString &file : groups[prefix])
189  {
190  QFileInfo fileInfo(gDoomseekerTemplatedPathResolver().resolve(file));
191  args() << fileInfo.absoluteFilePath();
192  }
193  }
194 }
195 
196 QString GameHost::fileLoadingPrefix(int index) const
197 {
198  const QString &pwad = params().pwadsPaths()[index];
199  bool optional = false;
200  if (params().pwadsOptional().size() > index)
201  {
202  optional = params().pwadsOptional()[index];
203  }
204 
205  if (pwad.toLower().endsWith(".deh"))
206  {
207  return argForDehLoading();
208  }
209  else if (pwad.toLower().endsWith(".bex"))
210  {
211  return argForBexLoading();
212  }
213 
214  if (optional)
215  return argForOptionalWadLoading();
216  return argForPwadLoading();
217 }
218 
219 const QString &GameHost::argForBexLoading() const
220 {
221  return d->argBexLoading;
222 }
223 
224 const QString &GameHost::argForDehLoading() const
225 {
226  return d->argDehLoading;
227 }
228 
229 const QString &GameHost::argForIwadLoading() const
230 {
231  return d->argIwadLoading;
232 }
233 
235 {
236  return d->argOptionalWadLoading;
237 }
238 
239 const QString &GameHost::argForPort() const
240 {
241  return d->argPort;
242 }
243 
244 const QString &GameHost::argForPwadLoading() const
245 {
246  return d->argPwadLoading;
247 }
248 
249 const QString &GameHost::argForDemoPlayback() const
250 {
251  return d->argDemoPlayback;
252 }
253 
254 const QString &GameHost::argForDemoRecord() const
255 {
256  return d->argDemoRecord;
257 }
258 
259 const QString &GameHost::argForServerLaunch() const
260 {
261  return d->argServerLaunch;
262 }
263 
264 QStringList &GameHost::args()
265 {
266  return d->currentCmdLine->args;
267 }
268 
270 {
271  BAIL_ON_ERROR(addGlobalGameCustomParameters());
272  BAIL_ON_ERROR(addIwad());
273  BAIL_ON_ERROR(addPwads());
274 
275  // Port
276  if (params().hostMode() == GameCreateParams::Host && params().port() > 0)
277  {
278  args() << argForPort() << QString::number(params().port());
279  }
280 
281  // CVars
282  const QList<GameCVar> &cvars = params().cvars();
283  for (const GameCVar &c : cvars)
284  {
285  if (c.value().type() == QVariant::Bool)
286  {
287  // Games like their bools passed in as 0/1 numbers.
288  args() << c.command() << QString::number(c.valueInt());
289  }
290  else
291  {
292  args() << c.command() << c.valueString();
293  }
294  }
295 
296  if (params().hostMode() == GameCreateParams::Host)
297  {
298  // Some games may not offer such argument.
299  if (!argForServerLaunch().isEmpty())
300  {
301  args() << argForServerLaunch();
302  }
303  }
304 
305  BAIL_ON_ERROR(addDMFlags());
306  BAIL_ON_ERROR(addExtra());
307  BAIL_ON_ERROR(addCustomParameters());
308 
309  addDemoPlaybackIfApplicable();
310  addDemoRecordIfApplicable();
311  saveDemoMetaData();
312 }
313 
315 {
316  d->message = Message();
317  d->currentCmdLine = &cmdLine;
318  d->params = params;
319 
320  args().clear();
321 
322  setupGamePaths();
323  if (d->message.isError())
324  {
325  return d->message;
326  }
327 
329  return d->message;
330 }
331 
333 {
334  CommandLineInfo cmdLine;
335 
336  Message message = createHostCommandLine(params, cmdLine);
337  if (!message.isIgnore())
338  {
339  return message;
340  }
341 
342  #ifdef Q_OS_WIN32
343  const bool WRAP_IN_SSS_CONSOLE = false;
344  #else
345  const bool WRAP_IN_SSS_CONSOLE = params.hostMode() == GameCreateParams::Host;
346  #endif
347 
348  if (WRAP_IN_SSS_CONSOLE)
349  {
350  QIcon icon;
351  if (plugin() != nullptr)
352  {
353  icon = plugin()->icon();
354  }
356  return Message();
357  }
358  else
359  {
360  return AppRunner::runExecutable(cmdLine);
361  }
362 }
363 
365 {
366  return d->params;
367 }
368 
370 {
371  return d->plugin;
372 }
373 
374 void GameHost::saveDemoMetaData()
375 {
376  if (params().demoRecord() == GameDemo::Managed)
377  {
378  GameDemo::saveDemoMetaData(params().demoPath(), *plugin(),
379  params().iwadName(), params().pwads());
380  }
381 }
382 
383 void GameHost::setArgForBexLoading(const QString &arg)
384 {
385  d->argBexLoading = arg;
386 }
387 
388 void GameHost::setArgForDehLoading(const QString &arg)
389 {
390  d->argDehLoading = arg;
391 }
392 
393 void GameHost::setArgForIwadLoading(const QString &arg)
394 {
395  d->argIwadLoading = arg;
396 }
397 
398 void GameHost::setArgForOptionalWadLoading(const QString &arg)
399 {
400  d->argOptionalWadLoading = arg;
401 }
402 
403 void GameHost::setArgForPort(const QString &arg)
404 {
405  d->argPort = arg;
406 }
407 
408 void GameHost::setArgForPwadLoading(const QString &arg)
409 {
410  d->argPwadLoading = arg;
411 }
412 
413 void GameHost::setArgForDemoPlayback(const QString &arg)
414 {
415  d->argDemoPlayback = arg;
416 }
417 
418 void GameHost::setArgForDemoRecord(const QString &arg)
419 {
420  d->argDemoRecord = arg;
421 }
422 
423 void GameHost::setArgForServerLaunch(const QString &arg)
424 {
425  d->argServerLaunch = arg;
426 }
427 
428 void GameHost::setMessage(const Message &message)
429 {
430  d->message = message;
431 }
432 
433 void GameHost::setupGamePaths()
434 {
435  const QString &exePath = params().executablePath();
436  if (exePath.trimmed().isEmpty())
437  {
438  setMessage(Message::customError(tr("The game executable is not set.")));
439  return;
440  }
441 
442  QFileInfo fileInfo(gDoomseekerTemplatedPathResolver().resolve(exePath));
443  if (!fileInfo.isFile() && !fileInfo.isBundle())
444  {
445  QString error = tr("The executable\n%1\ndoesn't exist or is not a file.")
446  .arg(fileInfo.filePath());
448  return;
449  }
450  d->currentCmdLine->executable = fileInfo;
451  d->currentCmdLine->applicationDir = fileInfo.dir();
452 }
453 
455 {
456  for (const QString &pwad : params().pwadsPaths())
457  {
458  QFileInfo fi(gDoomseekerTemplatedPathResolver().resolve(pwad));
459  if (!fi.isFile())
460  {
461  QString error = tr("PWAD path error:\n\"%1\" doesn't exist or is a directory!").arg(pwad);
463  return false;
464  }
465  }
466  return true;
467 }