source-engine/utils/demoinfo/demoinfo.cpp

528 lines
13 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: A simple application demonstrating the HL2 demo file format ( subject to change!!! )
//
// $NoKeywords: $
//=============================================================================//
#include <windows.h>
#include "tier0/dbg.h"
#include "filesystem.h"
#include "FileSystem_Tools.h"
#include "cmdlib.h"
#include "tooldemofile.h"
static bool uselogfile = false;
static bool spewed = false;
#define LOGFILE_NAME "log.txt"
#define COM_COPY_CHUNK_SIZE 8192
//-----------------------------------------------------------------------------
// Purpose: Prints to stdout and to the developer console and optionally to a log file
// Input : depth -
// *fmt -
// ... -
//-----------------------------------------------------------------------------
void vprint( int depth, const char *fmt, ... )
{
char string[ 8192 ];
va_list va;
va_start( va, fmt );
vsprintf( string, fmt, va );
va_end( va );
FILE *fp = NULL;
if ( uselogfile )
{
fp = fopen( LOGFILE_NAME, "ab" );
}
while ( depth-- > 0 )
{
printf( " " );
OutputDebugString( " " );
if ( fp )
{
fprintf( fp, " " );
}
}
::printf( "%s", string );
OutputDebugString( string );
if ( fp )
{
char *p = string;
while ( *p )
{
if ( *p == '\n' )
{
fputc( '\r', fp );
}
fputc( *p, fp );
p++;
}
fclose( fp );
}
}
//-----------------------------------------------------------------------------
// Purpose: Warning/Msg call back through this API
// Input : type -
// *pMsg -
// Output : SpewRetval_t
//-----------------------------------------------------------------------------
SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg )
{
spewed = true;
switch ( type )
{
default:
case SPEW_MESSAGE:
case SPEW_ASSERT:
case SPEW_LOG:
vprint( 0, "%s", pMsg );
break;
case SPEW_WARNING:
if ( verbose )
{
vprint( 0, "%s", pMsg );
}
break;
case SPEW_ERROR:
vprint( 0, "%s\n", pMsg );
break;
}
return SPEW_CONTINUE;
}
//-----------------------------------------------------------------------------
// Purpose: Shows usage information
//-----------------------------------------------------------------------------
void printusage( void )
{
vprint( 0, "usage: demoinfo <.dem file>\n\
\t-v = verbose output\n\
\t-l = log to file log.txt\n\
\ne.g.: demoinfo -v u:/hl2/hl2/foo.dem\n" );
// Exit app
exit( 1 );
}
//-----------------------------------------------------------------------------
// Purpose: Removes previous log file
//-----------------------------------------------------------------------------
void CheckLogFile( void )
{
if ( uselogfile )
{
_unlink( LOGFILE_NAME );
vprint( 0, " Outputting to log.txt\n" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Prints banner
//-----------------------------------------------------------------------------
void PrintHeader()
{
vprint( 0, "Valve Software - demoinfo.exe (%s)\n", __DATE__ );
vprint( 0, "--- Demo File Info Sample ---\n" );
}
//-----------------------------------------------------------------------------
// Purpose: Parses all "smoothing" info from .dem file
// Input : &demoFile -
// smooth -
//-----------------------------------------------------------------------------
void ParseSmoothingInfo( CToolDemoFile &demoFile, CUtlVector< demosmoothing_t >& smooth )
{
democmdinfo_t info;
int dummy;
bool demofinished = false;
while ( !demofinished )
{
int tick = 0;
byte cmd;
bool swallowmessages = true;
do
{
demoFile.ReadCmdHeader( cmd, tick );
// COMMAND HANDLERS
switch ( cmd )
{
case dem_synctick:
break;
case dem_stop:
{
swallowmessages = false;
demofinished = true;
}
break;
case dem_consolecmd:
{
demoFile.ReadConsoleCommand();
}
break;
case dem_datatables:
{
demoFile.ReadNetworkDataTables( NULL );
}
break;
case dem_usercmd:
{
demoFile.ReadUserCmd( NULL, dummy );
}
break;
default:
{
swallowmessages = false;
}
break;
}
}
while ( swallowmessages );
if ( demofinished )
{
// StopPlayback();
return;
}
int curpos = demoFile.GetCurPos();
demoFile.ReadCmdInfo( info );
demoFile.ReadSequenceInfo( dummy, dummy );
demoFile.ReadRawData( NULL, 0 );
demosmoothing_t smoothing_entry;
smoothing_entry.file_offset = curpos;
smoothing_entry.frametick = tick;
smoothing_entry.info = info;
smoothing_entry.samplepoint = false;
smoothing_entry.vecmoved = info.GetViewOrigin();
smoothing_entry.angmoved = info.GetViewAngles();
smoothing_entry.targetpoint = false;
smoothing_entry.vectarget = info.GetViewOrigin();
// Add to end of list
smooth.AddToTail( smoothing_entry );
}
}
//-----------------------------------------------------------------------------
// Purpose: Resets all smoothing data back to original values
// Input : smoothing -
//-----------------------------------------------------------------------------
void ClearSmoothingInfo( CSmoothingContext& smoothing )
{
int c = smoothing.smooth.Count();
int i;
for ( i = 0; i < c; i++ )
{
demosmoothing_t *p = &smoothing.smooth[ i ];
p->info.Reset();
p->vecmoved = p->info.GetViewOrigin();
p->angmoved = p->info.GetViewAngles();
p->samplepoint = false;
p->vectarget = p->info.GetViewOrigin();
p->targetpoint = false;
}
}
//-----------------------------------------------------------------------------
// Purpose: Helper for copying sub-chunk of file
// Input : dst -
// src -
// nSize -
//-----------------------------------------------------------------------------
void COM_CopyFileChunk( FileHandle_t dst, FileHandle_t src, int nSize )
{
int copysize = nSize;
char copybuf[COM_COPY_CHUNK_SIZE];
while (copysize > COM_COPY_CHUNK_SIZE)
{
g_pFileSystem->Read ( copybuf, COM_COPY_CHUNK_SIZE, src );
g_pFileSystem->Write( copybuf, COM_COPY_CHUNK_SIZE, dst );
copysize -= COM_COPY_CHUNK_SIZE;
}
g_pFileSystem->Read ( copybuf, copysize, src );
g_pFileSystem->Write( copybuf, copysize, dst );
g_pFileSystem->Flush ( src );
g_pFileSystem->Flush ( dst );
}
//-----------------------------------------------------------------------------
// Purpose: Writes out a new .dem file based on the existing dem file with new camera positions saved into the dem file
// Note: The new file is named filename_smooth.dem
// Input : *filename -
// smoothing -
//-----------------------------------------------------------------------------
void SaveSmoothingInfo( char const *filename, CSmoothingContext& smoothing )
{
// Nothing to do
int c = smoothing.smooth.Count();
if ( !c )
return;
IBaseFileSystem *fs = g_pFileSystem;
FileHandle_t infile, outfile;
infile = fs->Open( filename, "rb", "GAME" );
if ( infile == FILESYSTEM_INVALID_HANDLE )
return;
int filesize = fs->Size( infile );
char outfilename[ 512 ];
Q_StripExtension( filename, outfilename, sizeof( outfilename ) );
Q_strncat( outfilename, "_smooth", sizeof(outfilename), COPY_ALL_CHARACTERS );
Q_DefaultExtension( outfilename, ".dem", sizeof( outfilename ) );
outfile = fs->Open( outfilename, "wb", "GAME" );
if ( outfile == FILESYSTEM_INVALID_HANDLE )
{
fs->Close( infile );
return;
}
int i;
// The basic algorithm is to seek to each sample and "overwrite" it during copy with the new data...
int lastwritepos = 0;
for ( i = 0; i < c; i++ )
{
demosmoothing_t *p = &smoothing.smooth[ i ];
int copyamount = p->file_offset - lastwritepos;
COM_CopyFileChunk( outfile, infile, copyamount );
fs->Seek( infile, p->file_offset, FILESYSTEM_SEEK_HEAD );
// wacky hacky overwriting
fs->Write( &p->info, sizeof( democmdinfo_t ), outfile );
lastwritepos = fs->Tell( outfile );
fs->Seek( infile, p->file_offset + sizeof( democmdinfo_t ), FILESYSTEM_SEEK_HEAD );
}
// Copy the final bit of data, if any...
int final = filesize - lastwritepos;
COM_CopyFileChunk( outfile, infile, final );
fs->Close( outfile );
fs->Close( infile );
}
//-----------------------------------------------------------------------------
// Purpose: Helper for spewing verbose sample information
// Input : flags -
// Output : char const
//-----------------------------------------------------------------------------
char const *DescribeFlags( int flags )
{
static char outbuf[ 256 ];
outbuf[ 0 ] = 0;
if ( flags & FDEMO_USE_ORIGIN2 )
{
Q_strncat( outbuf, "USE_ORIGIN2, ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
}
if ( flags & FDEMO_USE_ANGLES2 )
{
Q_strncat( outbuf, "USE_ANGLES2, ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
}
if ( flags & FDEMO_NOINTERP )
{
Q_strncat( outbuf, "NOINTERP, ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
}
int len = Q_strlen( outbuf );
if ( len > 2 )
{
outbuf[ len - 2 ] = 0;
}
else
{
Q_strncpy( outbuf, "N/A", sizeof( outbuf ) );
}
return outbuf;
}
//-----------------------------------------------------------------------------
// Purpose: Loads up all camera samples from a .dem file into the passed in context.
// Input : *filename -
// smoothing -
//-----------------------------------------------------------------------------
void LoadSmoothingInfo( const char *filename, CSmoothingContext& smoothing )
{
char name[ MAX_OSPATH ];
Q_strncpy (name, filename, sizeof(name) );
Q_DefaultExtension( name, ".dem", sizeof( name ) );
CToolDemoFile demoFile;
if ( !demoFile.Open( filename, true ) )
{
Warning( "ERROR: couldn't open %s.\n", name );
return;
}
demoheader_t * header = demoFile.ReadDemoHeader();
if ( !header )
{
demoFile.Close();
return;
}
Msg( "\n\n" );
Msg( "--------------------------------------------------------------\n" );
Msg( "demofilestamp: '%s'\n", header->demofilestamp );
Msg( "demoprotocol: %i\n", header->demoprotocol );
Msg( "networkprotocol: %i\n", header->networkprotocol );
Msg( "servername: '%s'\n", header->servername );
Msg( "clientname: '%s'\n", header->clientname );
Msg( "mapname: '%s'\n", header->mapname );
Msg( "gamedirectory: '%s'\n", header->gamedirectory );
Msg( "playback_time: %f seconds\n", header->playback_time );
Msg( "playback_ticks: %i ticks\n", header->playback_ticks );
Msg( "playback_frames: %i frames\n", header->playback_frames );
Msg( "signonlength: %s\n", Q_pretifymem( header->signonlength ) );
smoothing.active = true;
Q_strncpy( smoothing.filename, name, sizeof(smoothing.filename) );
smoothing.smooth.RemoveAll();
ClearSmoothingInfo( smoothing );
ParseSmoothingInfo( demoFile, smoothing.smooth );
Msg( "--------------------------------------------------------------\n" );
Msg( "smoothing data: %i samples\n", smoothing.smooth.Count() );
if ( verbose )
{
int c = smoothing.smooth.Count();
for ( int i = 0; i < c; ++i )
{
demosmoothing_t& sample = smoothing.smooth[ i ];
Msg( "Sample %i:\n", i );
Msg( " file pos: %i\n", sample.file_offset );
Msg( " tick: %i\n", sample.frametick );
Msg( " flags: %s\n", DescribeFlags( sample.info.flags ) );
Msg( " Original Data:\n" );
Msg( " origin: %.4f %.4f %.4f\n", sample.info.viewOrigin.x, sample.info.viewOrigin.y, sample.info.viewOrigin.z );
Msg( " viewangles: %.4f %.4f %.4f\n", sample.info.viewAngles.x, sample.info.viewAngles.y, sample.info.viewAngles.z );
Msg( " localviewangles: %.4f %.4f %.4f\n", sample.info.localViewAngles.x, sample.info.localViewAngles.y, sample.info.localViewAngles.z );
Msg( " Resampled Data:\n" );
Msg( " origin: %.4f %.4f %.4f\n", sample.info.viewOrigin2.x, sample.info.viewOrigin2.y, sample.info.viewOrigin2.z );
Msg( " viewangles: %.4f %.4f %.4f\n", sample.info.viewAngles2.x, sample.info.viewAngles2.y, sample.info.viewAngles2.z );
Msg( " localviewangles: %.4f %.4f %.4f\n", sample.info.localViewAngles2.x, sample.info.localViewAngles2.y, sample.info.localViewAngles2.z );
Msg( "\n" );
}
}
demoFile.Close();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : argc -
// argv[] -
// Output : int
//-----------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
SpewOutputFunc( SpewFunc );
SpewActivate( "demoinfo", 2 );
int i = 1;
for ( i ; i<argc ; i++)
{
if ( argv[ i ][ 0 ] == '-' )
{
switch( argv[ i ][ 1 ] )
{
case 'l':
uselogfile = true;
break;
case 'v':
verbose = true;
break;
case 'g':
++i;
break;
default:
printusage();
break;
}
}
}
if ( argc < 2 || ( i != argc ) )
{
PrintHeader();
printusage();
}
CheckLogFile();
PrintHeader();
vprint( 0, " Info for %s..\n", argv[ i - 1 ] );
char workingdir[ 256 ];
workingdir[0] = 0;
Q_getwd( workingdir, sizeof( workingdir ) );
if ( !FileSystem_Init( NULL, 0, FS_INIT_FULL ) )
return 1;
// Add this so relative filenames work.
g_pFullFileSystem->AddSearchPath( workingdir, "game", PATH_ADD_TO_HEAD );
// Load the demo
CSmoothingContext context;
LoadSmoothingInfo( argv[ i - 1 ], context );
// Note to tool makers:
// Do your work here!!!
//Performsmoothing( context );
// Save out updated .dem file
// UNCOMMENT THIS TO ENABLE OUTPUTTING NEW .DEM FILES!!!
// SaveSmoothingInfo( argv[ i - 1 ], context );
FileSystem_Term();
return 0;
}