src/demo.cpp

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

mercurial