src/demo.cpp

changeset 6
67b6ef6917ba
child 8
e8f645d9f28f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,218 @@
+#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;
+}
\ No newline at end of file

mercurial