Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

418 lines
11 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// $Header: $
// $NoKeywords: $
//
//=============================================================================
// Valve includes
#include "appframework/tier2app.h"
#include "filesystem.h"
#include "icommandline.h"
#include "tier2/p4helpers.h"
#include "p4lib/ip4.h"
#include "tier1/KeyValues.h"
#include "tier1/utlbuffer.h"
#include "bsplib.h"
#include "lumpfiles.h"
#include "filesystem_tools.h"
#include "cmdlib.h"
#ifdef _DEBUG
#include <windows.h>
#undef GetCurrentDirectory
#endif
//-----------------------------------------------------------------------------
// Standard spew functions
//-----------------------------------------------------------------------------
static SpewRetval_t SpewStdout( SpewType_t spewType, char const *pMsg )
{
if ( !pMsg )
return SPEW_CONTINUE;
#ifdef _DEBUG
OutputDebugString( pMsg );
#endif
printf( pMsg );
fflush( stdout );
return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE;
}
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
class CMkEntityPatchApp : public CTier2SteamApp
{
typedef CTier2SteamApp BaseClass;
public:
// Methods of IApplication
virtual bool Create();
virtual bool PreInit( );
virtual int Main();
virtual void Destroy() {}
void PrintHelp( );
private:
};
DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CMkEntityPatchApp );
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
bool CMkEntityPatchApp::Create()
{
SpewOutputFunc( SpewStdout );
AppSystemInfo_t appSystems[] =
{
{ "p4lib.dll", P4_INTERFACE_VERSION },
{ "", "" } // Required to terminate the list
};
return AddSystems( appSystems );
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CMkEntityPatchApp::PreInit( )
{
MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
if ( !BaseClass::PreInit() )
return false;
if ( !g_pFullFileSystem )
{
Error( "// ERROR: sfmgen is missing a required interface!\n" );
return false;
}
// Add paths...
if ( !SetupSearchPaths( NULL, false, true ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Print help
//-----------------------------------------------------------------------------
void CMkEntityPatchApp::PrintHelp( )
{
Msg( "Usage: mkentitypatch [-nop4] [-vproject <path to gameinfo.txt>] <in .bsp file>\n" );
Msg( "\t-nop4\t: [Optional] Disables auto perforce checkout/add.\n" );
Msg( "\t-vproject\t: [Optional] Specifies path to a gameinfo.txt file (which mod to build for).\n" );
Msg( "\t Source .BSP file whose entity lump you wish to patch.\n" );
}
//-----------------------------------------------------------------------------
// Computes a full directory
//-----------------------------------------------------------------------------
static void ComputeFullPath( const char *pRelativeDir, char *pFullPath, int nBufLen )
{
if ( !Q_IsAbsolutePath( pRelativeDir ) )
{
char pDir[MAX_PATH];
if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
{
Q_ComposeFileName( pDir, pRelativeDir, pFullPath, nBufLen );
}
}
else
{
Q_strncpy( pFullPath, pRelativeDir, nBufLen );
}
Q_StripTrailingSlash( pFullPath );
// Ensure the output directory exists
g_pFullFileSystem->CreateDirHierarchy( pFullPath );
}
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
entity_t *FindEntity( KeyValues *pEntity )
{
int nHammerId = pEntity->GetInt( "id", INT_MIN );
if ( nHammerId != INT_MIN )
{
// First, look for hammerid
for ( int i = 0; i < num_entities; ++i )
{
int nId = IntForKeyWithDefault( &entities[i], "hammerid", INT_MIN );
if ( nId == nHammerId )
return &entities[i];
}
}
// Unfortunately, hammmerid appears to be a relatively new feature. Now, we must
// look for target name
int nMatch = -1;
const char *pTargetName = pEntity->GetString( "targetname" );
if ( pTargetName && pTargetName[0] )
{
// First, look for hammerid
for ( int i = 0; i < num_entities; ++i )
{
const char *pMatchTargetName = ValueForKey( &entities[i], "targetname" );
if ( !pMatchTargetName || !pMatchTargetName[0] )
continue;
if ( !V_stricmp( pTargetName, pMatchTargetName ) )
{
if ( nMatch >= 0 )
{
//Warning( "Encountered multiple entities that matched targetname %s!\n", pTargetName );
//return false;
nMatch = -1; // force a fallback to scanning classname and origin
break;
}
else
nMatch = i;
}
}
}
if ( nMatch >= 0 )
return &entities[nMatch];
// No target name? Well, let's try classname and origin.
const char *pClassName = pEntity->GetString( "classname" );
if ( pClassName && pClassName[0] )
{
// First, look for hammerid
for ( int i = 0; i < num_entities; ++i )
{
const char *pMatchClassName = ValueForKey( &entities[i], "classname" );
if ( !pMatchClassName || !pMatchClassName[0] )
continue;
if ( V_stricmp( pClassName, pMatchClassName ) )
continue;
const char *pOrigin = "(na)";
if ( V_stricmp( pClassName, "worldspawn" ) ) // allow worldspawn to match all
{
pOrigin = pEntity->GetString( "origin" );
const char *pMatchOrigin = ValueForKey( &entities[i], "origin" );
if ( !pMatchOrigin || !pMatchOrigin[0] )
continue;
if ( V_stricmp( pOrigin, pMatchOrigin ) )
continue;
}
if ( nMatch >= 0 )
{
Warning( "Encountered multiple entities that matched classname %s, origin %s!\n", pClassName, pOrigin );
return false;
}
nMatch = i;
}
}
if ( nMatch >= 0 )
return &entities[nMatch];
return NULL;
}
bool InsertEntity( entity_t *pEntity, KeyValues *pEntityKeys )
{
CUtlVector<KeyValues *> vecKVs;
for ( KeyValues *pKey = pEntityKeys->GetFirstValue(); pKey; pKey = pKey->GetNextValue() )
{
vecKVs.AddToTail( pKey );
}
FOR_EACH_VEC_BACK( vecKVs, i )
{
epair_t *e = (epair_t*)malloc( sizeof(epair_t) );
memset (e, 0, sizeof(epair_t));
const char *pName = vecKVs[i]->GetName();
if ( strlen(pName) >= MAX_KEY-1 )
{
Warning( "ParseEpar: token %s too long", pName );
return false;
}
e->key = copystring(pName);
const char *pValue = vecKVs[i]->GetString();
if ( strlen(pValue) >= MAX_VALUE-1 )
{
Warning( "ParseEpar: token %s too long", pValue );
return false;
}
e->value = copystring(pValue);
// strip trailing spaces
StripTrailing( e->key );
StripTrailing( e->value );
e->next = pEntity->epairs;
pEntity->epairs = e;
}
// Flatten everything ( specifically, 'connection' keys, necessary to
// make the patch file have the same format as the commentary files )
for ( KeyValues *pKey = pEntityKeys->GetFirstTrueSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() )
{
InsertEntity( pEntity, pKey );
}
return true;
}
bool InsertEntity( KeyValues *pEntity )
{
entity_t &entity = entities[ num_entities++ ];
return InsertEntity( &entity, pEntity );
}
bool ReplaceEntity( KeyValues *pEntity )
{
entity_t *pReplace = FindEntity( pEntity );
if ( !pReplace )
{
Warning( "Tried to replace an entity %s, origin %s, but couldn't find the original!\n", pEntity->GetString( "classname" ), pEntity->GetString( "origin" ) );
return false;
}
epair_t *pNext;
for ( epair_t *e = pReplace->epairs; e; e = pNext )
{
pNext = e->next;
free( e->key );
free( e->value );
free( e );
}
pReplace->epairs = NULL;
return InsertEntity( pReplace, pEntity );
}
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
int CMkEntityPatchApp::Main()
{
// Backward compat for bsplib
g_pFileSystem = g_pFullFileSystem;
// This bit of hackery allows us to access files on the harddrive
g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
if ( CommandLine()->CheckParm( "-h" ) || CommandLine()->CheckParm( "-help" ) || CommandLine()->ParmCount() == 1 )
{
PrintHelp();
return 0;
}
// The file name is the last argument
const char *pBSPFile = CommandLine()->GetParm( CommandLine()->ParmCount() - 1 );
if ( !pBSPFile || pBSPFile[0] == 0 || pBSPFile[0] == '-' )
{
PrintHelp();
return 0;
}
char pFullPath[MAX_PATH];
ComputeFullPath( pBSPFile, pFullPath, sizeof(pFullPath) );
char pBSPFileName[MAX_PATH];
char pPatchFileName[MAX_PATH];
char pOutputFileName[MAX_PATH];
V_strcpy( pBSPFileName, pFullPath );
V_strcpy( pPatchFileName, pFullPath );
V_SetExtension( pBSPFileName, ".bsp", sizeof(pBSPFileName) );
V_SetExtension( pPatchFileName, ".pat", sizeof(pPatchFileName) );
GenerateLumpFileName( pFullPath, pOutputFileName, sizeof(pOutputFileName), LUMP_ENTITIES );
if ( !g_pFullFileSystem->FileExists( pBSPFileName ) )
{
Warning( "BSP file %s doesn't exist!\n", pBSPFileName );
return 0;
}
if ( !g_pFullFileSystem->FileExists( pPatchFileName ) )
{
Warning( "BSP patch file %s doesn't exist!\n", pPatchFileName );
return 0;
}
KeyValues *pKeyValues = new KeyValues( "patch" );
if ( !pKeyValues->LoadFromFile( g_pFullFileSystem, pPatchFileName ) )
{
Warning( "Error parsing patch file %s!\n", pPatchFileName );
return 0;
}
LoadBSPFile( pFullPath );
ParseEntities();
for( int i = 0; i < num_entities; i++ )
{
entity_t *pCur = &entities[i];
epair_t *pNext = NULL;
epair_t *pPrev = NULL;
for ( epair_t *e = pCur->epairs; e; e = pNext )
{
pNext = e->next;
e->next = pPrev;
pPrev = e;
}
pCur->epairs = pPrev;
}
for ( KeyValues *pKey = pKeyValues->GetFirstTrueSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() )
{
const char *pKeyName = pKey->GetName();
if ( !V_stricmp( pKeyName, "entity" ) )
{
if ( !InsertEntity( pKey ) )
return 0;
}
else if ( !V_stricmp( pKeyName, "replace_entity" ) )
{
if ( !ReplaceEntity( pKey ) )
return 0;
}
}
// Do Perforce Stuff
if ( CommandLine()->FindParm( "-nop4" ) )
{
g_p4factory->SetDummyMode( true );
}
g_p4factory->SetOpenFileChangeList( "Entity Patch Files" );
CP4AutoAddFile p4AddBSP( pBSPFileName );
CP4AutoAddFile p4AddPatch( pPatchFileName );
CP4AutoEditAddFile p4AddOutput( pOutputFileName );
UnparseEntities();
WriteLumpToFile( pBSPFileName, LUMP_ENTITIES, 0, dentdata.Base(), dentdata.Count() );
pKeyValues->deleteThis();
return -1;
}