launcher/demo.cpp

changeset 46
07578e081ae8
parent 45
f5b526a3423a
child 48
e121ea9dba93
equal deleted inserted replaced
45:f5b526a3423a 46:07578e081ae8
1 /*
2 * ZCinema: Zandronum demo launcher
3 * Copyright (C) 2013-2015 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (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, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <QFile>
20 #include <QDataStream>
21 #include <QMessageBox>
22 #include <QProcess>
23 #include <commonlib/misc.h>
24 #include <commonlib/config.h>
25 #include <commonlib/version.h>
26 #include "demo.h"
27 #include "prompts.h"
28 #include "ui_demoprompt.h"
29
30 //
31 // -------------------------------------------------------------------------------------------------
32 //
33
34 QString uncolorize (const QString& in)
35 {
36 // TODO: Handle long-form colors like \c[Red]
37 QString out;
38 int skip = 0;
39
40 for (QChar c : in)
41 {
42 if (skip-- > 0)
43 continue;
44
45 if (c == QChar ('\034'))
46 {
47 skip = 1;
48 continue;
49 }
50
51 out += c;
52 }
53
54 return out;
55 }
56
57 //
58 // -------------------------------------------------------------------------------------------------
59 //
60
61 static QString tr (const char* msg)
62 {
63 return QObject::tr (msg);
64 }
65
66 //
67 // -------------------------------------------------------------------------------------------------
68 //
69
70 static void error (QString msg)
71 {
72 QMessageBox::critical (NULL, "Error", msg);
73 }
74
75 //
76 // -------------------------------------------------------------------------------------------------
77 //
78
79 static ZandronumVersion findVersion (QString versionName)
80 {
81 QList<QVariant> versions = Config::get ("versions").toList();
82
83 for (int i = 0; i < versions.size(); ++i)
84 {
85 if (not versions[i].canConvert<ZandronumVersion>())
86 continue;
87
88 ZandronumVersion version = versions[i].value<ZandronumVersion>();
89
90 if (version.name == versionName
91 or version.name + "M" == versionName
92 or version.name == versionName + "M")
93 {
94 return version;
95 }
96 }
97
98 return ZandronumVersion();
99 }
100
101 //
102 // -------------------------------------------------------------------------------------------------
103 //
104
105 static QString findWAD (QString name)
106 {
107 QStringList wadpaths = Config::get ("wadpaths").toStringList();
108
109 if (wadpaths.empty())
110 {
111 error (tr ("No WAD paths configured!"));
112
113 // Cannot just return an empty string here since that'd trigger another error prompt - skip
114 // ahead and exit.
115 exit (1);
116 }
117
118 for (int i = 0; i < wadpaths.size(); ++i)
119 {
120 QString fullpath = QString ("%1/%2").arg (wadpaths[i]).arg (name);
121 QFile f (fullpath);
122
123 if (f.exists())
124 return fullpath;
125 }
126
127 return "";
128 }
129
130 //
131 // -------------------------------------------------------------------------------------------------
132 //
133
134 QString readString (QDataStream& stream)
135 {
136 QString out;
137 uint8 ch;
138
139 for (stream >> ch; ch != 0; stream >> ch)
140 out += QChar (ch);
141
142 return out;
143 }
144
145 //
146 // -------------------------------------------------------------------------------------------------
147 //
148
149 struct UserInfo
150 {
151 QString netname;
152 QString skin;
153 QString className;
154 uint32 color;
155 uint32 aimdist;
156 uint32 railcolor;
157 uint8 gender;
158 uint8 handicap;
159 uint8 unlagged;
160 uint8 respawnOnFire;
161 uint8 ticsPerUpdate;
162 uint8 connectionType;
163 };
164
165 struct DemoHeaders
166 {
167 uint8 length;
168 uint8 version;
169 uint8 userInfo;
170 uint8 bodyStart;
171 uint8 wads;
172 };
173
174 int launchDemo (QString path)
175 {
176 QFile f (path);
177
178 if (not f.open (QIODevice::ReadOnly))
179 {
180 error (tr ("Couldn't open '%1' for reading: %2").arg (path).arg (strerror (errno)));
181 return 1;
182 }
183
184 QDataStream stream (&f);
185 stream.setByteOrder (QDataStream::LittleEndian);
186
187 DemoHeaders headers;
188 uint32 length;
189 uint16 zanversionID, numWads;
190 uint32 longSink;
191 QString zanversion;
192 QStringList wads;
193 UserInfo userinfo;
194
195 // Assume a release build if the build ID not supplied. The demo only got the "ZCLD" signature
196 // in the 1.1 release build, 1.1.1 had no testing binaries and the build ID is included in 1.2
197 // onward.
198 BuildType buildID = ReleaseBuild;
199
200 bool ready = false;
201
202 // Check signature
203 {
204 uint32 demosignature;
205 stream >> demosignature;
206
207 if (demosignature != makeByteID ('Z', 'C', 'L', 'D'))
208 {
209 error (tr ("'%1' is not a valid Zandronum demo file!").arg (path));
210 return 1;
211 }
212 }
213
214 stream >> headers.length;
215 stream >> length;
216
217 // The remaining headers are variable and relative to the length header.
218 headers.version = headers.length + 1;
219 headers.userInfo = headers.length + 3,
220 headers.bodyStart = headers.length + 4;
221 headers.wads = headers.length + 10;
222
223 // Read the demo header and get data
224 for (;;)
225 {
226 uint8 header;
227 stream >> header;
228
229 if (header == headers.bodyStart)
230 {
231 ready = true;
232 break;
233 }
234 else if (header == headers.version)
235 {
236 stream >> zanversionID;
237 zanversion = readString (stream);
238
239 if (not zanversion.startsWith ("1.1-") and not zanversion.startsWith ("1.1.1-"))
240 {
241 uint8 a;
242 stream >> a;
243 buildID = (BuildType) a;
244 }
245
246 // The demo wads header accidentally changed in 1.3. :(
247 if (zanversion.left(1).toInt() >= 2 or zanversion.startsWith ("1.3"))
248 headers.wads = headers.length + 8;
249
250 stream >> longSink; // rng seed - we don't need it
251 }
252 else if (header == headers.userInfo)
253 {
254 userinfo.netname = readString (stream);
255 stream >> userinfo.gender;
256 stream >> userinfo.color;
257 stream >> userinfo.aimdist;
258 userinfo.skin = readString (stream);
259 stream >> userinfo.railcolor;
260 stream >> userinfo.handicap;
261 stream >> userinfo.unlagged;
262 stream >> userinfo.respawnOnFire;
263 stream >> userinfo.ticsPerUpdate;
264 stream >> userinfo.connectionType;
265 userinfo.className = readString (stream);
266 }
267 else if (header == headers.wads)
268 {
269 QString sink;
270 stream >> numWads;
271
272 for (uint8 i = 0; i < numWads; ++i)
273 {
274 QString wad = readString (stream);
275 wads << wad;
276 }
277
278 // The demo has two checksum strings. We're not interested in them, though.
279 (sink = readString (stream)) = readString (stream);
280 }
281 else
282 {
283 error (tr ("Unknown header %1!\n").arg (int (header)));
284 return 1;
285 }
286 }
287
288 if (not ready)
289 {
290 error (tr ("Incomplete demo header in '%s'!").arg (path));
291 return 1;
292 }
293
294 ZandronumVersion version = findVersion (zanversion);
295
296 if (version.name.isNull())
297 {
298 QDialog* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == ReleaseBuild));
299
300 if (not prompt->exec())
301 return 1;
302 }
303
304 QString iwadpath;
305 QStringList pwadpaths;
306
307 // Find the WADs
308 for (const QString& wad : wads)
309 {
310 QString path = findWAD (wad);
311
312 // WAD names are case-sensitive under non-Windows and they can appear in uppercase
313 // so we need to test that too.
314 if (path.isEmpty())
315 path = findWAD (wad.toUpper());
316
317 if (path.isEmpty())
318 {
319 error (tr ("Couldn't find %1!").arg (wad));
320 return 1;
321 }
322
323 if (&wad == &wads.first())
324 iwadpath = path;
325 else
326 pwadpaths << path;
327 }
328
329 if (not Config::get ("noprompt").toBool())
330 {
331 QString pwadtext;
332
333 for (const QString& wad : wads)
334 {
335 if (&wad == &wads.first())
336 continue; // skip the IWAD
337
338 if (not pwadtext.isEmpty())
339 pwadtext += "<br />";
340
341 pwadtext += wad;
342 }
343
344 QDialog* dlg = new QDialog;
345 Ui_DemoPrompt ui;
346 ui.setupUi (dlg);
347 ui.demoNameLabel->setText (basename (path));
348 ui.demoRecorder->setText (uncolorize (userinfo.netname));
349 ui.versionLabel->setText (zanversion);
350 ui.iwadLabel->setText (wads[0]);
351 ui.pwadsLabel->setText (pwadtext);
352 dlg->setWindowTitle (versionSignature());
353
354 if (not dlg->exec())
355 return 0;
356 }
357
358 QStringList cmdlineList;
359 cmdlineList << "-playdemo" << path << "-iwad" << iwadpath;
360
361 if (pwadpaths.size() > 0)
362 cmdlineList << "-file" << pwadpaths;
363
364 // print ("Executing: %1 %2\n", binarypath, cmdlineList.join (" "));
365 QProcess* proc = new QProcess;
366 proc->start (version.binaryPath, cmdlineList);
367 proc->waitForFinished (-1);
368 return 0;
369 }

mercurial