--- /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