diff -r f5b526a3423a -r 07578e081ae8 launcher/demo.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/demo.cpp Sat Jun 06 23:12:59 2015 +0300
@@ -0,0 +1,369 @@
+/*
+ * ZCinema: Zandronum demo launcher
+ * Copyright (C) 2013-2015 Teemu Piippo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "demo.h"
+#include "prompts.h"
+#include "ui_demoprompt.h"
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+QString uncolorize (const QString& in)
+{
+ // TODO: Handle long-form colors like \c[Red]
+ QString out;
+ int skip = 0;
+
+ for (QChar c : in)
+ {
+ if (skip-- > 0)
+ continue;
+
+ if (c == QChar ('\034'))
+ {
+ skip = 1;
+ continue;
+ }
+
+ out += c;
+ }
+
+ return out;
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+static QString tr (const char* msg)
+{
+ return QObject::tr (msg);
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+static void error (QString msg)
+{
+ QMessageBox::critical (NULL, "Error", msg);
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+static ZandronumVersion findVersion (QString versionName)
+{
+ QList versions = Config::get ("versions").toList();
+
+ for (int i = 0; i < versions.size(); ++i)
+ {
+ if (not versions[i].canConvert())
+ continue;
+
+ ZandronumVersion version = versions[i].value();
+
+ if (version.name == versionName
+ or version.name + "M" == versionName
+ or version.name == versionName + "M")
+ {
+ return version;
+ }
+ }
+
+ return ZandronumVersion();
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+static QString findWAD (QString name)
+{
+ QStringList wadpaths = Config::get ("wadpaths").toStringList();
+
+ if (wadpaths.empty())
+ {
+ error (tr ("No WAD paths configured!"));
+
+ // Cannot just return an empty string here since that'd trigger another error prompt - skip
+ // ahead and exit.
+ exit (1);
+ }
+
+ for (int i = 0; i < wadpaths.size(); ++i)
+ {
+ QString fullpath = QString ("%1/%2").arg (wadpaths[i]).arg (name);
+ QFile f (fullpath);
+
+ if (f.exists())
+ return fullpath;
+ }
+
+ return "";
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+QString readString (QDataStream& stream)
+{
+ QString out;
+ uint8 ch;
+
+ for (stream >> ch; ch != 0; stream >> ch)
+ out += QChar (ch);
+
+ return out;
+}
+
+//
+// -------------------------------------------------------------------------------------------------
+//
+
+struct UserInfo
+{
+ QString netname;
+ QString skin;
+ QString className;
+ uint32 color;
+ uint32 aimdist;
+ uint32 railcolor;
+ uint8 gender;
+ uint8 handicap;
+ uint8 unlagged;
+ uint8 respawnOnFire;
+ uint8 ticsPerUpdate;
+ uint8 connectionType;
+};
+
+struct DemoHeaders
+{
+ uint8 length;
+ uint8 version;
+ uint8 userInfo;
+ uint8 bodyStart;
+ uint8 wads;
+};
+
+int launchDemo (QString path)
+{
+ QFile f (path);
+
+ if (not f.open (QIODevice::ReadOnly))
+ {
+ error (tr ("Couldn't open '%1' for reading: %2").arg (path).arg (strerror (errno)));
+ return 1;
+ }
+
+ QDataStream stream (&f);
+ stream.setByteOrder (QDataStream::LittleEndian);
+
+ DemoHeaders headers;
+ uint32 length;
+ uint16 zanversionID, numWads;
+ uint32 longSink;
+ QString zanversion;
+ QStringList wads;
+ UserInfo userinfo;
+
+ // Assume a release build if the build ID not supplied. The demo only got the "ZCLD" signature
+ // in the 1.1 release build, 1.1.1 had no testing binaries and the build ID is included in 1.2
+ // onward.
+ BuildType buildID = ReleaseBuild;
+
+ bool ready = false;
+
+ // Check signature
+ {
+ uint32 demosignature;
+ stream >> demosignature;
+
+ if (demosignature != makeByteID ('Z', 'C', 'L', 'D'))
+ {
+ error (tr ("'%1' is not a valid Zandronum demo file!").arg (path));
+ return 1;
+ }
+ }
+
+ stream >> headers.length;
+ stream >> length;
+
+ // The remaining headers are variable and relative to the length header.
+ headers.version = headers.length + 1;
+ headers.userInfo = headers.length + 3,
+ headers.bodyStart = headers.length + 4;
+ headers.wads = headers.length + 10;
+
+ // Read the demo header and get data
+ for (;;)
+ {
+ uint8 header;
+ stream >> header;
+
+ if (header == headers.bodyStart)
+ {
+ ready = true;
+ break;
+ }
+ else if (header == headers.version)
+ {
+ stream >> zanversionID;
+ zanversion = readString (stream);
+
+ if (not zanversion.startsWith ("1.1-") and not zanversion.startsWith ("1.1.1-"))
+ {
+ uint8 a;
+ stream >> a;
+ buildID = (BuildType) a;
+ }
+
+ // The demo wads header accidentally changed in 1.3. :(
+ if (zanversion.left(1).toInt() >= 2 or zanversion.startsWith ("1.3"))
+ headers.wads = headers.length + 8;
+
+ stream >> longSink; // rng seed - we don't need it
+ }
+ else if (header == headers.userInfo)
+ {
+ userinfo.netname = readString (stream);
+ stream >> userinfo.gender;
+ stream >> userinfo.color;
+ stream >> userinfo.aimdist;
+ userinfo.skin = readString (stream);
+ stream >> userinfo.railcolor;
+ stream >> userinfo.handicap;
+ stream >> userinfo.unlagged;
+ stream >> userinfo.respawnOnFire;
+ stream >> userinfo.ticsPerUpdate;
+ stream >> userinfo.connectionType;
+ userinfo.className = readString (stream);
+ }
+ else if (header == headers.wads)
+ {
+ QString sink;
+ stream >> numWads;
+
+ for (uint8 i = 0; i < numWads; ++i)
+ {
+ QString wad = readString (stream);
+ wads << wad;
+ }
+
+ // The demo has two checksum strings. We're not interested in them, though.
+ (sink = readString (stream)) = readString (stream);
+ }
+ else
+ {
+ error (tr ("Unknown header %1!\n").arg (int (header)));
+ return 1;
+ }
+ }
+
+ if (not ready)
+ {
+ error (tr ("Incomplete demo header in '%s'!").arg (path));
+ return 1;
+ }
+
+ ZandronumVersion version = findVersion (zanversion);
+
+ if (version.name.isNull())
+ {
+ QDialog* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == ReleaseBuild));
+
+ if (not prompt->exec())
+ return 1;
+ }
+
+ QString iwadpath;
+ QStringList pwadpaths;
+
+ // Find the WADs
+ for (const QString& wad : wads)
+ {
+ QString path = findWAD (wad);
+
+ // WAD names are case-sensitive under non-Windows and they can appear in uppercase
+ // so we need to test that too.
+ if (path.isEmpty())
+ path = findWAD (wad.toUpper());
+
+ if (path.isEmpty())
+ {
+ error (tr ("Couldn't find %1!").arg (wad));
+ return 1;
+ }
+
+ if (&wad == &wads.first())
+ iwadpath = path;
+ else
+ pwadpaths << path;
+ }
+
+ if (not Config::get ("noprompt").toBool())
+ {
+ QString pwadtext;
+
+ for (const QString& wad : wads)
+ {
+ if (&wad == &wads.first())
+ continue; // skip the IWAD
+
+ if (not pwadtext.isEmpty())
+ pwadtext += "
";
+
+ pwadtext += wad;
+ }
+
+ QDialog* dlg = new QDialog;
+ Ui_DemoPrompt ui;
+ ui.setupUi (dlg);
+ ui.demoNameLabel->setText (basename (path));
+ ui.demoRecorder->setText (uncolorize (userinfo.netname));
+ ui.versionLabel->setText (zanversion);
+ ui.iwadLabel->setText (wads[0]);
+ ui.pwadsLabel->setText (pwadtext);
+ dlg->setWindowTitle (versionSignature());
+
+ if (not dlg->exec())
+ return 0;
+ }
+
+ QStringList cmdlineList;
+ cmdlineList << "-playdemo" << path << "-iwad" << iwadpath;
+
+ if (pwadpaths.size() > 0)
+ cmdlineList << "-file" << pwadpaths;
+
+ // print ("Executing: %1 %2\n", binarypath, cmdlineList.join (" "));
+ QProcess* proc = new QProcess;
+ proc->start (version.binaryPath, cmdlineList);
+ proc->waitForFinished (-1);
+ return 0;
+}