src/demo.cpp

Fri, 05 Jun 2015 18:33:51 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Fri, 05 Jun 2015 18:33:51 +0300
changeset 37
c82a86ea87be
parent 36
b8fa9171be6e
child 38
db677d321cf4
permissions
-rw-r--r--

Major rework, lots of internal maintenance, version editor removed

/*
 *  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 "demo.h"
#include "misc.h"
#include "ui_demoprompt.h"
#include "prompts.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 bool isKnownVersion (QString ver)
{
	for (const QVariant& it : getVersions())
	{
		if (it.toString() == ver || it.toString() + 'M' == ver)
			return true;
	}
	
	return false;
}

//
// -------------------------------------------------------------------------------------------------
//

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 (9);
	}

	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;
};

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);

	uint8 offset;
	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;
		}
	}
	
	// Zandronum stores CLD_DEMOLENGTH after the signature. This is also the
	// first demo enumerator, so we can determine the offset (which is variable!)
	// from this byte
	stream >> offset
	       >> length;
	
	// Read the demo header and get data
	for (;;)
	{
		uint8 header;
		stream >> header;
		
		if (header == DemoBodyStart + offset)
		{
			ready = true;
			break;
		}
		else if (header == DemoVersion + offset)
		{
			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;
			}

			stream >> longSink; // rng seed - we don't need it
		}
		else if (header == DemoUserInfo + offset)
		{
			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 == DemoWads + offset)
		{
			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. Down the sink they go...
			for (int i = 0; i < 2; ++i)
				sink = 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;
	}
	
	if (not isKnownVersion (zanversion))
	{
		QDialog* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == ReleaseBuild));

		if (not prompt->exec())
			return 1;
	}

	QString binarypath = Config::get ("binarypaths").toMap()[zanversion].toString();

	if (binarypath.isEmpty())
	{
		error (tr ("No binary path specified for Zandronum version %1!").arg (zanversion));
		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 (binarypath, cmdlineList);
	proc->waitForFinished (-1);
	return 0;
}

mercurial