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