Now capable of actually launching demos

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
parent 5
3c04e05ab24f
child 7
fdabb5869e5f

Now capable of actually launching demos

src/bytestream.cpp file | annotate | diff | comparison | revisions
src/bytestream.h file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/config.h file | annotate | diff | comparison | revisions
src/demo.cpp file | annotate | diff | comparison | revisions
src/demo.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/misc.cpp file | annotate | diff | comparison | revisions
src/misc.h file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bytestream.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,225 @@
+#include "bytestream.h"
+#include "misc.h"
+#include <string.h>
+
+union {
+	uint32 i;
+	float f;
+} g_floatunion;
+
+Bytestream::Bytestream( ulong len ) {
+	m_data = null;
+	resize( len );
+	clear();
+}
+
+Bytestream::Bytestream( const char* data, ulong len ) {
+	m_data = null;
+	init( data, len );
+}
+
+void Bytestream::resize( ulong newsize ) {
+	char* olddata = null;
+	ulong oldsize;
+	
+	if( m_data ) {
+		oldsize = m_size;
+		olddata = new char[oldsize];
+		memcpy( olddata, m_data, oldsize );
+	}
+	
+	delete[] m_data;
+	m_data = new uint8[newsize];
+	m_size = newsize;
+	
+	if( olddata )
+		memcpy( m_data, olddata, min<ulong> ( oldsize, newsize ));
+}
+
+void Bytestream::init( const char* data, ulong len ) {
+	resize( len );
+	memcpy( m_data, data, len );
+	m_ptr = &m_data[0];
+	m_len = len;
+}
+
+size_t Bytestream::len() const {
+	return m_len;
+}
+
+void Bytestream::clear() {
+	m_ptr = &m_data[0];
+	m_len = 0;
+}
+
+uint8& Bytestream::subscript( ulong idx ) {
+	return m_data[idx];
+}
+
+const uint8& Bytestream::const_subscript( ulong idx ) const {
+	return m_data[idx];
+}
+
+void Bytestream::seek( ulong pos ) {
+	m_ptr = m_data + pos;
+}
+
+// =============================================================================
+void Bytestream::rewind() {
+	m_ptr = m_data;
+}
+
+ulong Bytestream::bytesLeft() const {
+	return ( m_len - ( m_ptr - &m_data[0] ));
+}
+
+ulong Bytestream::spaceLeft() const {
+	return ( m_size - m_len );
+}
+
+// =============================================================================
+bool Bytestream::readByte( uint8& val ) {
+	if( bytesLeft() < 1 )
+		return false;
+	
+	val = *m_ptr++;
+	return true;
+}
+
+// =============================================================================
+bool Bytestream::readShort( uint16& val ) {
+	if( bytesLeft() < 2 )
+		return false;
+	
+	val = 0;
+	
+	for( int i = 0; i < 2; ++i )
+		val |= *m_ptr++ << ( i * 8 );
+
+	return true;
+}
+
+// =============================================================================
+bool Bytestream::readLong( uint32& val ) {
+	if( bytesLeft() < 4 )
+		return false;
+	
+	val = 0;
+	
+	for( int i = 0; i < 4; ++i )
+		val |= *m_ptr++ << ( i * 8 );
+	
+	return true;
+}
+
+// =============================================================================
+bool Bytestream::readFloat( float& val ) {
+	if( !readLong( g_floatunion.i ))
+		return false;
+
+	val = g_floatunion.f;
+	return true;
+}
+
+// =============================================================================
+bool Bytestream::readString( str& val ) {
+	if( bytesLeft() < 1 ) // need at least the null terminator
+		return false;
+	
+	uint8_t c;
+	
+	while( readByte( c ) && c != '\0' )
+		val += ( char ) c;
+	
+	return true;
+}
+
+// =============================================================================
+void Bytestream::doWrite( uint8_t val ) {
+	*m_ptr++ = val;
+	m_len++;
+}
+
+void Bytestream::growToFit( ulong bytes ) {
+	if( spaceLeft() < bytes )
+		resize( m_size + bytes + 128 );
+}
+
+bool Bytestream::readBytes( uint8 numbytes, uint8* val ) {
+	while( numbytes-- )
+		if( !readByte( *val++ ))
+			return false;
+	
+	return true;
+}
+
+void Bytestream::writeBytes( uint8 numbytes, const uint8* val ) {
+	growToFit( numbytes );
+	
+	while( numbytes-- )
+		writeByte( *val++ );
+}
+
+// =============================================================================
+void Bytestream::writeByte( uint8 val ) {
+	growToFit( 1 );
+	doWrite( val );
+}
+
+// =============================================================================
+void Bytestream::writeShort( uint16 val ) {
+	growToFit( 2 );
+	
+	for( int i = 0; i < 2; ++i )
+		doWrite(( val >> ( i * 8 )) & 0xFF );
+}
+
+// =============================================================================
+void Bytestream::writeLong( uint32 val ) {
+	growToFit( 4 );
+	
+	for( int i = 0; i < 4; ++i )
+		doWrite(( val >> ( i * 8 )) & 0xFF );
+}
+
+// =============================================================================
+void Bytestream::writeFloat( float val ) {
+	g_floatunion.f = val;
+	writeLong( g_floatunion.i );
+}
+
+// =============================================================================
+void Bytestream::writeString( str val ) {
+	growToFit( val.length() + 1 );
+	
+	for( qchar c : val )
+		doWrite( c.toAscii() );
+	
+	doWrite( '\0' );
+}
+
+// =============================================================================
+bool Bytestream::tryMerge( const Bytestream& other ) {
+	if( spaceLeft() < other.len() )
+		return false;
+	
+	for( ulong i = 0; i < other.len(); ++i )
+		writeByte( other[i] );
+	
+	return true;
+}
+
+void Bytestream::merge( const Bytestream& other ) {
+	growToFit( other.len() );
+	
+	if( !tryMerge( other )) {
+		// Shouldn't happen
+		fprint( stderr, "ByteStream: Not enough space for merge (%1 bytes left, need %2)",
+				spaceLeft(), other.len() );
+		abort();
+	}
+}
+
+const uint8* Bytestream::data() const {
+	return m_data;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bytestream.h	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,62 @@
+#ifndef BYTESTREAM_H
+#define BYTESTREAM_H
+
+#include "types.h"
+
+class Bytestream {
+private:
+	uint8* m_data;
+	uint8* m_ptr;
+	ulong m_size, m_len;
+	
+	void doWrite (uint8_t val);
+	
+public:
+	Bytestream (ulong len = 2048);
+	Bytestream (const char* data, ulong len);
+	
+	void init (const char* data, ulong len);
+	void rewind ();
+	void seek (ulong pos);
+	void clear ();
+	void merge (const Bytestream& other);
+	bool tryMerge (const Bytestream& other);
+	void resize (ulong len);
+	size_t len () const;
+	ulong bytesLeft () const;
+	ulong spaceLeft () const;
+	void growToFit (ulong bytes);
+	const uint8* data () const;
+	
+	bool readBytes (uint8 numbytes, uint8* val);
+	bool readByte (uint8& val);
+	bool readShort (uint16& val);
+	bool readLong (uint32& val);
+	bool readString (str& val);
+	bool readFloat (float& val);
+	
+	void writeBytes (uint8 numbytes, const uint8* val);
+	void writeByte (uint8 val);
+	void writeShort (uint16 val);
+	void writeLong (uint32 val);
+	void writeFloat (float val);
+	void writeString (str val);
+	
+	Bytestream& operator<< (const Bytestream& other) {
+		merge (other);
+		return *this;
+	}
+	
+	uint8& subscript (ulong idx);
+	const uint8& const_subscript (ulong idx) const;
+	
+	uint8& operator[] (ulong idx) {
+		return subscript (idx);
+	}
+	
+	const uint8& operator[] (ulong idx) const {
+		return const_subscript (idx);
+	}
+};
+
+#endif // BYTESTREAM_H
\ No newline at end of file
--- a/src/config.cpp	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/config.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -3,6 +3,7 @@
 #include <QFormLayout>
 #include "config.h"
 #include "ui_configbox.h"
+#include "misc.h"
 
 // =============================================================================
 // -----------------------------------------------------------------------------
@@ -150,10 +151,4 @@
 // -----------------------------------------------------------------------------
 void ConfigBox::cancelPressed() {
 	reject();
-}
-
-// =============================================================================
-// -----------------------------------------------------------------------------
-str ConfigBox::binaryConfigName( str ver ) const {
-	return fmt( "binaries/%1", ver );
 }
\ No newline at end of file
--- a/src/config.h	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/config.h	Wed Jul 17 18:46:47 2013 +0300
@@ -16,7 +16,6 @@
 	virtual ~ConfigBox();
 	void addPath( str path );
 	void initFromSettings();
-	str binaryConfigName( str ver ) const;
 	
 public slots:
 	void addPath();
--- /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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo.h	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,21 @@
+#ifndef DEMO_H
+#define DEMO_H
+#include "types.h"
+
+enum {
+	DemoLength,
+	DemoVersion,
+	DemoCVars,
+	DemoUserInfo,
+	DemoBodyStart,
+	DemoTiccmd,
+	DemoInvUse,
+	DemoCenterView,
+	DemoTaunt,
+	DemoEnd,
+	DemoWads
+};
+
+int launchDemo( str path );
+
+#endif // DEMO_H
\ No newline at end of file
--- a/src/main.cpp	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/main.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -2,9 +2,10 @@
 #include <QSettings>
 #include "types.h"
 #include "config.h"
+#include "demo.h"
 
 const list<str> g_zanVersions ({
-	"1.1",
+	"1.1-r130716-1906M",
 });
 
 // =============================================================================
@@ -15,8 +16,6 @@
 	app.setOrganizationName( UNIXNAME );
 	app.setApplicationVersion( versionString() );
 	
-	print( "Settings path: %1\n", QSettings().fileName() );
-	
 	for( int i = 1; i < argc; ++i ) {
 		str arg = argv[i];
 		
@@ -26,7 +25,13 @@
 		}
 	}
 	
-	return app.exec();
+	if( argc != 2 ) {
+		fprint( stderr, "Usage: %1 <demo>   - Launch a demo file\n", argv[0] );
+		fprint( stderr, "       %1 --config - Configure " APPNAME "\n", argv[0] );
+		return 255;
+	}
+	
+	return launchDemo( argv[1] );
 }
 
 // =============================================================================
--- a/src/main.h	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/main.h	Wed Jul 17 18:46:47 2013 +0300
@@ -15,6 +15,8 @@
 #define BUILD_RC       3
 #define BUILD_RELEASE  4
 
+#define elif else if
+
 #include <QSettings>
 
 static const std::nullptr_t null = nullptr;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,11 @@
+#include "misc.h"
+
+uint32 makeByteID( uint8 a, uint8 b, uint8 c, uint8 d ) {
+	return a | ( b << 8 ) | ( c << 16 ) | ( d << 24 );
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+str binaryConfigName( str ver ) {
+	return fmt( "binaries/%1", ver );
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc.h	Wed Jul 17 18:46:47 2013 +0300
@@ -0,0 +1,30 @@
+#ifndef MISC_H
+#define MISC_H
+
+#include "types.h"
+
+uint32 makeByteID( uint8 a, uint8 b, uint8 c, uint8 d );
+str binaryConfigName( str ver );
+
+// -----------------------------------------------------------------------------
+// Templated clamp
+template<class T> static inline T clamp (T a, T min, T max) {
+	return (a > max) ? max : (a < min) ? min : a;
+}
+
+// Templated minimum
+template<class T> static inline T min (T a, T b) {
+	return (a < b) ? a : b;
+}
+
+// Templated maximum
+template<class T> static inline T max (T a, T b) {
+	return (a > b) ? a : b;
+}
+
+// Templated absolute value
+template<class T> static inline T abs (T a) {
+	return (a >= 0) ? a : -a;
+}
+
+#endif // MISC_H
\ No newline at end of file
--- a/src/types.cpp	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/types.cpp	Wed Jul 17 18:46:47 2013 +0300
@@ -14,8 +14,8 @@
 	return text;
 }
 
-void doPrint( initlist<StringFormatArg> args ) {
-	printf( "%s", doFormat( args ).toStdString().c_str() );
+void doPrint( FILE* fp, initlist<StringFormatArg> args ) {
+	fprintf( fp, "%s", doFormat( args ).toStdString().c_str() );
 }
 
 // =============================================================================
--- a/src/types.h	Wed Jul 17 03:06:21 2013 +0300
+++ b/src/types.h	Wed Jul 17 18:46:47 2013 +0300
@@ -16,6 +16,19 @@
 template<class T> using initlist = std::initializer_list<T>;
 using std::size_t;
 
+typedef qint8 int8;
+typedef qint16 int16;
+typedef qint32 int32;
+typedef qint64 int64;
+typedef quint8 uint8;
+typedef quint16 uint16;
+typedef quint32 uint32;
+typedef quint64 uint64;
+
+#ifdef IN_IDE_PARSER // :|
+typedef void FILE;
+#endif
+
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 // =============================================================================
@@ -53,13 +66,18 @@
 	template<class T> StringFormatArg( const list<T>& v ) {
 		m_val = "{ ";
 		uint i = 0;
+		const bool isString = typeid( T ) == typeid( str );
 		
 		for( const T& it : v ) {
 			if( i++ )
 				m_val += ", ";
 			
 			StringFormatArg arg( it );
-			m_val += arg.value();
+			
+			if( isString )
+				m_val += "\"" + arg.value() + "\"";
+			else
+				m_val += arg.value();
 		}
 		
 		if( i )
@@ -79,15 +97,17 @@
 str doFormat( initlist<StringFormatArg> args );
 
 // printf replacement
-void doPrint( initlist<StringFormatArg> args );
+void doPrint( FILE* fp, initlist<StringFormatArg> args );
 
 // Macros to access these functions
 #ifndef IN_IDE_PARSER
 # define fmt(...) doFormat({ __VA_ARGS__ })
-# define print(...) doPrint({ __VA_ARGS__ })
+# define print(...) doPrint( stdout, { __VA_ARGS__ })
+# define fprint(FP, ...) doPrint( FP, { __VA_ARGS__ })
 #else
 str fmt( const char* fmtstr, ... );
 void print( const char* fmtstr, ... );
+void fprint( FILE* fp, const char* fmtstr, ... );
 #endif
 
 #endif // TYPES_H
\ No newline at end of file

mercurial