//========= 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 #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 ] \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 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; }