launcher/demo.cpp

changeset 46
07578e081ae8
parent 45
f5b526a3423a
child 48
e121ea9dba93
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <QFile>
+#include <QDataStream>
+#include <QMessageBox>
+#include <QProcess>
+#include <commonlib/misc.h>
+#include <commonlib/config.h>
+#include <commonlib/version.h>
+#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<QVariant> versions = Config::get ("versions").toList();
+
+	for (int i = 0; i < versions.size(); ++i)
+	{
+		if (not versions[i].canConvert<ZandronumVersion>())
+			continue;
+
+		ZandronumVersion version = versions[i].value<ZandronumVersion>();
+
+		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 += "<br />";
+
+			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;
+}

mercurial