src/demo.cpp

Sun, 08 Sep 2013 16:53:31 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sun, 08 Sep 2013 16:53:31 +0300
changeset 21
99225eac33ba
parent 20
a5457405cc9b
child 22
2fe0b7e0da7b
permissions
-rw-r--r--

use proper names QVariant and QChar instead of ambigous/dumb "var" and "qchar"

/*
 *  ZanDemo: Zandronum demo launcher
 *  Copyright (C) 2013 Santeri 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"

EXTERN_CONFIG (Map,  binaryPaths)
EXTERN_CONFIG (List, wadpaths)
EXTERN_CONFIG (Bool, noprompt)

static const uint32 g_demoSignature = makeByteID ('Z', 'C', 'L', 'D');

// =============================================================================
// -----------------------------------------------------------------------------
str uncolorize (const str& in) {
	str out;
	int skip = 0;
	
	for (const QChar& c : in) {
		if (skip-- > 0)
			continue;
		
		if (c == QChar ('\034')) {
			skip = 1;
			continue;
		}
		
		out += c;
	}
	
	return out;
}

// =============================================================================
// -----------------------------------------------------------------------------
static str tr (const char* msg) {
	return QObject::tr (msg);
}

// =============================================================================
// -----------------------------------------------------------------------------
static void error (str msg) {
	QMessageBox::critical (null, "Error", msg);
}

// =============================================================================
// -----------------------------------------------------------------------------
static bool isKnownVersion (str ver) {
	for (const QVariant& it : getVersions())
		if (it.toString() == ver || it.toString() + 'M' == ver)
			return true;
	
	return false;
}

// =============================================================================
// -----------------------------------------------------------------------------
static str findWAD (str name) {
	if (cfg::wadpaths.size() == 0) {
		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 (const QVariant& it : cfg::wadpaths) {
		str fullpath = fmt ("%1/%2", it.toString(), name);
		QFile f (fullpath);
		
		if (f.exists())
			return fullpath;
	}
	
	return "";
}

// =============================================================================
// -----------------------------------------------------------------------------
QDataStream& operator>> (QDataStream& stream, str& out) {
	uint8 c;
	out = "";
	
	for (stream >> c; c != '\0'; stream >> c)
		out += c;
	
	return stream;
}

// =============================================================================
// -----------------------------------------------------------------------------
int launchDemo (str path) {
	QFile f (path);
	
	if (!f.open (QIODevice::ReadOnly)) {
		error (fmt (tr ("Couldn't open '%1' for reading: %2"), path, strerror (errno)));
		return 1;
	}
	
	QDataStream stream (&f);
	stream.setByteOrder (QDataStream::LittleEndian);
	
	uint8 offset;
	uint32 length;
	uint16 zanversionID, numWads;
	uint32 longSink;
	str zanversion;
	list<str> wads;
	uint8 buildID;
	bool ready = false;
	
	struct {
		str netname, skin, className;
		uint8 gender, handicap, unlagged, respawnOnFire, ticsPerUpdate, connectionType;
		uint32 color, aimdist, railcolor;
	} userinfo;
	
	// Check signature
	{
		uint32 sig;
		
		stream >> sig;
		if (sig != g_demoSignature) {
			error (fmt (tr ("'%1' is not a Zandronum demo file!"), path));
			return 3;
		}
	}
	
	// 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;
		print ("header: %1\n", (int) header);
		
		if (header == DemoBodyStart + offset) {
			ready = true;
			break;
		} elif (header == DemoVersion + offset) {
			print ("Read demo version\n");
			stream >> zanversionID
			       >> zanversion;
			
			print ("version ID: %1, version: %2\n", zanversionID, zanversion);
			
			if (zanversion.left (4) != "1.1-" && zanversion.left (6) != "1.1.1-")
				stream >> buildID;
			else {
				// Assume a release build if not supplied. The demo only got the
				// "ZCLD" signature in the 1.1 release build, 1.1.1 had no
				// development binaries and the build ID will be included in 1.1.2.
				buildID = 1;
			}
			
			stream >> longSink; // rng seed - we don't need it
		} elif (header == DemoUserInfo + offset) {
			print ("Read userinfo\n");
			stream >> userinfo.netname
			       >> userinfo.gender
			       >> userinfo.color
			       >> userinfo.aimdist
			       >> userinfo.skin
			       >> userinfo.railcolor
			       >> userinfo.handicap
			       >> userinfo.unlagged
			       >> userinfo.respawnOnFire
			       >> userinfo.ticsPerUpdate
			       >> userinfo.connectionType
			       >> userinfo.className;
		} elif (header == DemoWads + offset) {
			str sink;
			stream >> numWads;
			
			for (uint8 i = 0; i < numWads; ++i) {
				str wad;
				stream >> wad;
				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)
				stream >> sink;
		} else {
			error (fmt (tr ("Unknown header %1!\n"), (int) header));
			return 3;
		}
	}

	if (!ready) {
		error (fmt (tr ("Incomplete demo header in '%s'!"), path));
		return 3;
	}
	
	if (!isKnownVersion (zanversion)) {
		UnknownVersionPrompt* prompt = new UnknownVersionPrompt (path, zanversion, (buildID == 1));
		if (!prompt->exec())
			return 6;
		
		if (!isKnownVersion (zanversion)) {
			error (tr ("Failure in configuration! This shouldn't happen."));
			return 6;
		}
	}
	
	str binarypath = cfg::binaryPaths [zanversion].toString();
	
	if (binarypath.isEmpty()) {
		error (fmt (tr ("No binary path specified for Zandronum version %1!"), zanversion));
		return 7;
	}
	
	str iwadpath;
	list<str> pwadpaths;
	
	// Find the WADs
	for (const str& wad : wads) {
		str path = findWAD (wad);
		
		// IWAD names can appear in uppercase too. Linux is case-sensitive, so..
		if (&wad == &wads[0] && path.isEmpty())
			path = findWAD (wad.toUpper());
		
		if (path.isEmpty()) {
			error (fmt (tr ("Couldn't find %1!"), wad));
			return 8;
		}
		
		if (&wad == &wads[0])
			iwadpath = path;
		else
			pwadpaths << path;
	}
	
	if (!cfg::noprompt) {
		str pwadtext;
		
		for (const str& pwad : wads) {
			if (&pwad == &wads[0])
				continue; // skip the IWAD
			
			if (!pwadtext.isEmpty())
				pwadtext += "<br />";
			
			pwadtext += pwad;
		}
		
		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 (fmt (APPNAME " %1", versionString()));
		
		if (!dlg->exec())
			return 1;
	}

	QStringList cmdlineList ({
		"-playdemo", path,
		"-iwad", iwadpath,
	});
	
	if (pwadpaths.size() > 0) {
		cmdlineList << "-file";
		cmdlineList << pwadpaths;
	}
	
	print ("Executing: %1 %2\n", binarypath, cmdlineList.join (" "));
	QProcess* proc = new QProcess;
	proc->start (binarypath, cmdlineList);
	proc->waitForFinished (-1);
	return 0;
}

mercurial