src/demo.cpp

Wed, 17 Jul 2013 18:46:47 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Wed, 17 Jul 2013 18:46:47 +0300
changeset 6
67b6ef6917ba
child 8
e8f645d9f28f
permissions
-rw-r--r--

Now capable of actually launching demos

#include <QFile>
#include <QDataStream>
#include <QMessageBox>
#include <QProcess>
#include "demo.h"
#include "bytestream.h"
#include "misc.h"

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

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

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

// =============================================================================
// -----------------------------------------------------------------------------
static int getVersionIndex( str ver ) {
	int i;
	
	for( i = 0; i < g_zanVersions.size(); ++i )
		if( g_zanVersions[i] == ver )
			return i;
	
	return -1;
}

// =============================================================================
// -----------------------------------------------------------------------------
static str findWAD( str name ) {
	QSettings cfg;
	list<var> paths = cfg.value( "wads/paths", list<var>() ).toList();
	
	if( paths.size() == 0 ) {
		error( tr( "No WAD paths configured!" ));
		exit( 9 );
	}
	
	for( var it : paths ) {
		str fullpath = fmt( "%1/%2", it.toString(), name );
		QFile f( fullpath );
		
		if( f.exists() )
			return fullpath;
	}
	
	return "";
}

// =============================================================================
// -----------------------------------------------------------------------------
int launchDemo( str path ) {
	FILE* fp = fopen( path.toStdString().c_str(), "r" );
	
	if( !fp ) {
		error( fmt( tr( "Couldn't open '%1' for reading: %2" ), path, strerror( errno )));
		return 1;
	}
	
	fseek( fp, 0, SEEK_END );
	const size_t fsize = ftell( fp );
	rewind( fp );
	
	char* buf = new char[fsize];
	
	if( fread( buf, 1, fsize, fp ) != fsize ) {
		error( tr( "I/O error" ));
		delete[] buf;
		return 2;
	}
	
	Bytestream s( buf, fsize );
	delete[] buf;
	
	uint8 offset;
	uint32 length;
	
	struct {
		str netname, skin, className;
		uint8 gender, handicap, unlagged, respawnOnFire, ticsPerUpdate, connectionType;
		uint32 color, aimdist, railcolor;
	} userinfo;
	
	// Check signature
	{
		uint32 sig;
		
		if( !s.readLong( sig ) || 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
	s.readByte( offset );
	s.readLong( length );
	
	uint16 zanversionID, numWads;
	uint32 longSink;
	str zanversion;
	list<str> wads;
	bool ready = false;
	
	for( ;; ) {
		uint8 header;
		if( !s.readByte( header ))
			break;
		
		if( header == DemoBodyStart + offset ) {
			ready = true;
			break;
		} elif( header == DemoVersion + offset ) {
			s.readShort( zanversionID );
			s.readString( zanversion );
			s.readLong( longSink ); // rng seed - we don't need it
		} elif( header == DemoUserInfo + offset ) {
			s.readString( userinfo.netname );
			s.readByte( userinfo.gender );
			s.readLong( userinfo.color );
			s.readLong( userinfo.aimdist );
			s.readString( userinfo.skin );
			s.readLong( userinfo.railcolor );
			s.readByte( userinfo.handicap );
			s.readByte( userinfo.unlagged );
			s.readByte( userinfo.respawnOnFire );
			s.readByte( userinfo.connectionType );
			s.readString( userinfo.className );
		} elif( header == DemoWads + offset ) {
			s.readShort( numWads );
			
			for( uint8 i = 0; i < numWads; ++i ) {
				str wad;
				s.readString( wad );
				wads << wad;
			}
			
			// The demo has two checksum strings. We're not interested
			// in them though.
			str sink;
			for( int i = 0; i < 2; ++i )
				s.readString( sink );
		} else {
			error( fmt( tr( "Unknown header %1!\n" ), (int) header ));
			return 4;
		}
	}
	
	if( !ready ) {
		error( fmt( tr( "Incomplete demo header in '%s'!" ), path ));
		return 5;
	}
	
	int i = getVersionIndex( zanversion );
	if( i == -1 ) {
		error( fmt( tr( "Unknown Zandronum version %1!\n" ), zanversion ));
		return 6;
	}
	
	QSettings cfg;
	str binarypath = cfg.value( binaryConfigName( zanversion )).toString();
	
	if( binarypath.isEmpty() ) {
		error( fmt( tr( "No binary path specified for Zandronum version %1!\n" ), 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!\n" ), wad ));
			return 8;
		}
		
		if( &wad == &wads[0] )
			iwadpath = path;
		else
			pwadpaths << path;
	}
	
	print( "binary: %1\n", binarypath );
	print( "iwad: %1\npwads: %2\n", iwadpath, pwadpaths );
	
	QStringList cmdlineList ({
		"-iwad",
		iwadpath,
		"-playdemo",
		path
	});
	
	if( pwadpaths.size() > 0 ) {
		cmdlineList << "-file";
		cmdlineList << pwadpaths;
	}
	
	print( "commandline: %1 %2\n", binarypath, cmdlineList.join( " " ));
	QProcess* proc = new QProcess;
	proc->start( binarypath, cmdlineList );
	proc->waitForFinished( -1 );
	return 0;
}

mercurial