# HG changeset patch # User Teemu Piippo # Date 1374076007 -10800 # Node ID 67b6ef6917ba34c34b3712b05340bc6c8846e4e4 # Parent 3c04e05ab24f469f6b34349d14f0e562eda52dac Now capable of actually launching demos diff -r 3c04e05ab24f -r 67b6ef6917ba src/bytestream.cpp --- /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 + +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 ( 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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/bytestream.h --- /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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/config.cpp --- 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 #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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/config.h --- 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(); diff -r 3c04e05ab24f -r 67b6ef6917ba src/demo.cpp --- /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 +#include +#include +#include +#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 paths = cfg.value( "wads/paths", list() ).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 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 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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/demo.h --- /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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/main.cpp --- 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 #include "types.h" #include "config.h" +#include "demo.h" const list 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 - Launch a demo file\n", argv[0] ); + fprint( stderr, " %1 --config - Configure " APPNAME "\n", argv[0] ); + return 255; + } + + return launchDemo( argv[1] ); } // ============================================================================= diff -r 3c04e05ab24f -r 67b6ef6917ba src/main.h --- 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 static const std::nullptr_t null = nullptr; diff -r 3c04e05ab24f -r 67b6ef6917ba src/misc.cpp --- /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 diff -r 3c04e05ab24f -r 67b6ef6917ba src/misc.h --- /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 static inline T clamp (T a, T min, T max) { + return (a > max) ? max : (a < min) ? min : a; +} + +// Templated minimum +template static inline T min (T a, T b) { + return (a < b) ? a : b; +} + +// Templated maximum +template static inline T max (T a, T b) { + return (a > b) ? a : b; +} + +// Templated absolute value +template static inline T abs (T a) { + return (a >= 0) ? a : -a; +} + +#endif // MISC_H \ No newline at end of file diff -r 3c04e05ab24f -r 67b6ef6917ba src/types.cpp --- 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 args ) { - printf( "%s", doFormat( args ).toStdString().c_str() ); +void doPrint( FILE* fp, initlist args ) { + fprintf( fp, "%s", doFormat( args ).toStdString().c_str() ); } // ============================================================================= diff -r 3c04e05ab24f -r 67b6ef6917ba src/types.h --- 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 using initlist = std::initializer_list; 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 StringFormatArg( const list& 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 args ); // printf replacement -void doPrint( initlist args ); +void doPrint( FILE* fp, initlist 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