Team Fortress 2 Source Code as on 22/4/2020
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.

540 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // vmf_tweak.cpp : Defines the entry point for the console application.
  9. //
  10. #include "stdafx.h"
  11. #include "chunkfile.h"
  12. #include "utllinkedlist.h"
  13. #include <stdlib.h>
  14. #include "tier1/strtools.h"
  15. #include "tier0/icommandline.h"
  16. #include "tier1/utlrbtree.h"
  17. #include "tier1/utlbuffer.h"
  18. #include "tier1/KeyValues.h"
  19. #include "filesystem.h"
  20. #include "FileSystem_Tools.h"
  21. #include "cmdlib.h"
  22. #include <windows.h>
  23. char const *g_pInFilename = NULL;
  24. char *CopyString( char const *pStr );
  25. unsigned long g_CurLoadOrder = 0;
  26. //-----------------------------------------------------------------------------
  27. // default spec function
  28. //-----------------------------------------------------------------------------
  29. SpewRetval_t VMFTweakSpewFunc( SpewType_t spewType, char const *pMsg )
  30. {
  31. OutputDebugString( pMsg );
  32. printf( pMsg );
  33. switch( spewType )
  34. {
  35. case SPEW_MESSAGE:
  36. case SPEW_WARNING:
  37. case SPEW_LOG:
  38. return SPEW_CONTINUE;
  39. case SPEW_ASSERT:
  40. case SPEW_ERROR:
  41. default:
  42. return SPEW_DEBUGGER;
  43. }
  44. }
  45. void logprint( char const *logfile, const char *fmt, ... )
  46. {
  47. char string[ 8192 ];
  48. va_list va;
  49. va_start( va, fmt );
  50. vsprintf( string, fmt, va );
  51. va_end( va );
  52. FILE *fp = NULL;
  53. static bool first = false;
  54. if ( first )
  55. {
  56. first = false;
  57. fp = fopen( logfile, "wb" );
  58. }
  59. else
  60. {
  61. fp = fopen( logfile, "ab" );
  62. }
  63. if ( fp )
  64. {
  65. char *p = string;
  66. while ( *p )
  67. {
  68. if ( *p == '\n' )
  69. {
  70. fputc( '\r', fp );
  71. }
  72. fputc( *p, fp );
  73. p++;
  74. }
  75. fclose( fp );
  76. }
  77. }
  78. class CChunkKeyBase
  79. {
  80. public:
  81. unsigned long m_LoadOrder; // Which order it appeared in the file.
  82. };
  83. class CKeyValue : public CChunkKeyBase
  84. {
  85. public:
  86. void SetKey( const char *pStr )
  87. {
  88. delete [] m_pKey;
  89. m_pKey = CopyString( pStr );
  90. }
  91. void SetValue( const char *pStr )
  92. {
  93. delete [] m_pValue;
  94. m_pValue = CopyString( pStr );
  95. }
  96. public:
  97. char *m_pKey;
  98. char *m_pValue;
  99. };
  100. class CChunk : public CChunkKeyBase
  101. {
  102. public:
  103. // Returns true if the chunk has the specified key and if its value
  104. // is equal to pValueStr (case-insensitive).
  105. bool CompareKey( char const *pKeyName, char const *pValueStr );
  106. // Look for a key by name.
  107. CKeyValue* FindKey( char const *pKeyName );
  108. CKeyValue* FindKey( char const *pKeyName, const char *pValue );
  109. CChunk* FindChunk( char const *pKeyName );
  110. // Find a key by name, and replace any occurences with the new name.
  111. void RenameKey( const char *szOldName, const char *szNewName );
  112. public:
  113. char *m_pChunkName;
  114. CUtlLinkedList<CKeyValue*, unsigned short> m_Keys;
  115. CUtlLinkedList<CChunk*, unsigned short> m_Chunks;
  116. };
  117. CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne );
  118. CChunk *g_pCurChunk = 0;
  119. CChunkFile *g_pChunkFile = 0;
  120. int g_DotCounter = 0;
  121. // --------------------------------------------------------------------------------- //
  122. // CChunk implementation.
  123. // --------------------------------------------------------------------------------- //
  124. bool CChunk::CompareKey( char const *pKeyName, char const *pValueStr )
  125. {
  126. CKeyValue *pKey = FindKey( pKeyName );
  127. if( pKey && stricmp( pKey->m_pValue, pValueStr ) == 0 )
  128. return true;
  129. else
  130. return false;
  131. }
  132. CKeyValue* CChunk::FindKey( char const *pKeyName )
  133. {
  134. for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) )
  135. {
  136. CKeyValue *pKey = m_Keys[i];
  137. if( stricmp( pKey->m_pKey, pKeyName ) == 0 )
  138. return pKey;
  139. }
  140. return NULL;
  141. }
  142. CChunk* CChunk::FindChunk( char const *pChunkName )
  143. {
  144. for( unsigned short i=m_Chunks.Head(); i != m_Chunks.InvalidIndex(); i=m_Chunks.Next(i) )
  145. {
  146. CChunk *pChunk = m_Chunks[i];
  147. if( stricmp( pChunk->m_pChunkName, pChunkName ) == 0 )
  148. return pChunk;
  149. }
  150. return NULL;
  151. }
  152. CKeyValue* CChunk::FindKey( char const *pKeyName, const char *pValue )
  153. {
  154. for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) )
  155. {
  156. CKeyValue *pKey = m_Keys[i];
  157. if( stricmp( pKey->m_pKey, pKeyName ) == 0 && stricmp( pKey->m_pValue, pValue ) == 0 )
  158. return pKey;
  159. }
  160. return NULL;
  161. }
  162. void CChunk::RenameKey( const char *szOldName, const char *szNewName )
  163. {
  164. if ((!szOldName) || (!szNewName))
  165. return;
  166. CKeyValue *pKey = FindKey( szOldName );
  167. if ( pKey )
  168. {
  169. delete pKey->m_pKey;
  170. pKey->m_pKey = CopyString( szNewName );
  171. }
  172. }
  173. // --------------------------------------------------------------------------------- //
  174. // Util functions.
  175. // --------------------------------------------------------------------------------- //
  176. char *CopyString( char const *pStr )
  177. {
  178. char *pRet = new char[ strlen(pStr) + 1 ];
  179. strcpy( pRet, pStr );
  180. return pRet;
  181. }
  182. ChunkFileResult_t MyDefaultHandler( CChunkFile *pFile, void *pData, char const *pChunkName )
  183. {
  184. CChunk *pChunk = ParseChunk( pChunkName, true );
  185. g_pCurChunk->m_Chunks.AddToTail( pChunk );
  186. return ChunkFile_Ok;
  187. }
  188. ChunkFileResult_t MyKeyHandler( const char *szKey, const char *szValue, void *pData )
  189. {
  190. // Add the key to the current chunk.
  191. CKeyValue *pKey = new CKeyValue;
  192. pKey->m_LoadOrder = g_CurLoadOrder++;
  193. pKey->m_pKey = CopyString( szKey );
  194. pKey->m_pValue = CopyString( szValue );
  195. g_pCurChunk->m_Keys.AddToTail( pKey );
  196. return ChunkFile_Ok;
  197. }
  198. CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne )
  199. {
  200. // Add the new chunk.
  201. CChunk *pChunk = new CChunk;
  202. pChunk->m_pChunkName = CopyString( pChunkName );
  203. pChunk->m_LoadOrder = g_CurLoadOrder++;
  204. // Parse it out..
  205. CChunk *pOldChunk = g_pCurChunk;
  206. g_pCurChunk = pChunk;
  207. //if( ++g_DotCounter % 16 == 0 )
  208. // printf( "." );
  209. while( 1 )
  210. {
  211. if( g_pChunkFile->ReadChunk( MyKeyHandler ) != ChunkFile_Ok )
  212. break;
  213. if( bOnlyOne )
  214. break;
  215. }
  216. g_pCurChunk = pOldChunk;
  217. return pChunk;
  218. }
  219. CChunk* ReadChunkFile( char const *pInFilename )
  220. {
  221. CChunkFile chunkFile;
  222. if( chunkFile.Open( pInFilename, ChunkFile_Read ) != ChunkFile_Ok )
  223. {
  224. printf( "Error opening chunk file %s for reading.\n", pInFilename );
  225. return NULL;
  226. }
  227. printf( "Reading.." );
  228. chunkFile.SetDefaultChunkHandler( MyDefaultHandler, 0 );
  229. g_pChunkFile = &chunkFile;
  230. CChunk *pRet = ParseChunk( "***ROOT***", false );
  231. printf( "\n\n" );
  232. return pRet;
  233. }
  234. class CChunkHolder
  235. {
  236. public:
  237. static bool SortChunkFn( const CChunkHolder &a, const CChunkHolder &b )
  238. {
  239. return a.m_LoadOrder < b.m_LoadOrder;
  240. }
  241. unsigned long m_LoadOrder;
  242. CKeyValue *m_pKey;
  243. CChunk *m_pChunk;
  244. };
  245. void WriteChunks_R( CChunkFile *pFile, CChunk *pChunk, bool bRoot )
  246. {
  247. if( !bRoot )
  248. {
  249. pFile->BeginChunk( pChunk->m_pChunkName );
  250. }
  251. // Sort them..
  252. CUtlRBTree<CChunkHolder,int> sortedStuff( 0, 0, &CChunkHolder::SortChunkFn );
  253. // Write keys.
  254. for( unsigned short i=pChunk->m_Keys.Head(); i != pChunk->m_Keys.InvalidIndex(); i = pChunk->m_Keys.Next( i ) )
  255. {
  256. CChunkHolder holder;
  257. holder.m_pKey = pChunk->m_Keys[i];
  258. holder.m_LoadOrder = holder.m_pKey->m_LoadOrder;
  259. holder.m_pChunk = NULL;
  260. sortedStuff.Insert( holder );
  261. }
  262. // Write subchunks.
  263. for( int i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) )
  264. {
  265. CChunkHolder holder;
  266. holder.m_pChunk = pChunk->m_Chunks[i];
  267. holder.m_LoadOrder = holder.m_pChunk->m_LoadOrder;
  268. holder.m_pKey = NULL;
  269. sortedStuff.Insert( holder );
  270. }
  271. // Write stuff in sorted order.
  272. int i = sortedStuff.FirstInorder();
  273. if ( i != sortedStuff.InvalidIndex() )
  274. {
  275. while ( 1 )
  276. {
  277. CChunkHolder &h = sortedStuff[i];
  278. if ( h.m_pKey )
  279. {
  280. pFile->WriteKeyValue( h.m_pKey->m_pKey, h.m_pKey->m_pValue );
  281. }
  282. else
  283. {
  284. WriteChunks_R( pFile, h.m_pChunk, false );
  285. }
  286. if ( i == sortedStuff.LastInorder() )
  287. break;
  288. i = sortedStuff.NextInorder( i );
  289. }
  290. }
  291. if( !bRoot )
  292. {
  293. pFile->EndChunk();
  294. }
  295. }
  296. bool WriteChunkFile( char const *pOutFilename, CChunk *pRoot )
  297. {
  298. CChunkFile chunkFile;
  299. if( chunkFile.Open( pOutFilename, ChunkFile_Write ) != ChunkFile_Ok )
  300. {
  301. printf( "Error opening chunk file %s for writing.\n", pOutFilename );
  302. return false;
  303. }
  304. printf( "Writing.." );
  305. WriteChunks_R( &chunkFile, pRoot, true );
  306. printf( "\n\n" );
  307. return true;
  308. }
  309. //
  310. //
  311. // EXAMPLE
  312. //
  313. //
  314. void ScanRopeSlack( CChunk *pChunk )
  315. {
  316. if( stricmp( pChunk->m_pChunkName, "entity" ) == 0 )
  317. {
  318. if( pChunk->CompareKey( "classname", "keyframe_rope" ) ||
  319. pChunk->CompareKey( "classname", "move_rope" ) )
  320. {
  321. CKeyValue *pKey = pChunk->FindKey( "slack" );
  322. if( pKey )
  323. {
  324. // Subtract 100 from all the Slack properties.
  325. float flCur = (float)atof( pKey->m_pValue );
  326. char str[256];
  327. sprintf( str, "%f", flCur + 100 );
  328. pKey->m_pValue = CopyString( str );
  329. }
  330. }
  331. }
  332. }
  333. int g_nLogicAutoReplacementsMade = 0;
  334. void LogicAuto( CChunk *pChunk )
  335. {
  336. if ( pChunk->CompareKey( "classname", "logic_auto" ) )
  337. {
  338. CChunk *pConnections = pChunk->FindChunk( "connections" );
  339. if ( !pConnections )
  340. return;
  341. bool bFound = false;
  342. for ( int i=0; i < pConnections->m_Keys.Count(); i++ )
  343. {
  344. CKeyValue *pTestKV = pConnections->m_Keys[i];
  345. if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue )
  346. {
  347. bFound = true;
  348. break;
  349. }
  350. }
  351. if ( !bFound )
  352. return;
  353. ++g_nLogicAutoReplacementsMade;
  354. // Ok, this is a logic_auto with OnMapSpawn outputs in its connections.
  355. CUtlLinkedList<CKeyValue*,int> newKeys;
  356. FOR_EACH_LL( pConnections->m_Keys, i )
  357. {
  358. CKeyValue *pTestKV = pConnections->m_Keys[i];
  359. if ( V_stricmp( pTestKV->m_pKey, "OnMapSpawn" ) == 0 )
  360. {
  361. if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue )
  362. {
  363. CKeyValue *pNewKV = new CKeyValue;
  364. pNewKV->SetKey( "OnMapTransition" );
  365. pNewKV->SetValue( pTestKV->m_pValue );
  366. newKeys.AddToTail( pNewKV );
  367. }
  368. }
  369. }
  370. FOR_EACH_LL( newKeys, i )
  371. {
  372. if ( !pConnections->FindKey( "OnMapTransition", newKeys[i]->m_pValue ) )
  373. {
  374. pConnections->m_Keys.AddToTail( newKeys[i] );
  375. }
  376. }
  377. // Fix spawnflags.
  378. CKeyValue *pFlags = pChunk->FindKey("spawnflags");
  379. if ( pFlags )
  380. {
  381. unsigned long curVal;
  382. sscanf( pFlags->m_pValue, "%lu", &curVal );
  383. if ( curVal & 1 )
  384. {
  385. --curVal;
  386. char str[512];
  387. sprintf( str, "%lu", curVal );
  388. pFlags->SetValue( str );
  389. }
  390. }
  391. }
  392. }
  393. void ScanChunks( CChunk *pChunk, void (*fn)(CChunk *) )
  394. {
  395. fn( pChunk );
  396. // Recurse into the children.
  397. for( unsigned short i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) )
  398. {
  399. ScanChunks( pChunk->m_Chunks[i], fn );
  400. }
  401. }
  402. int main(int argc, char* argv[])
  403. {
  404. CommandLine()->CreateCmdLine( argc, argv );
  405. SpewOutputFunc( VMFTweakSpewFunc );
  406. if( argc < 2 )
  407. {
  408. printf( "vmf_tweak <input file> [output file]\n" );
  409. return 1;
  410. }
  411. g_pInFilename = argv[1];
  412. char const *pOutFilename = argc >= 3 ? argv[2] : "";
  413. CChunk *pRoot = ReadChunkFile( g_pInFilename );
  414. if( !pRoot )
  415. return 2;
  416. //
  417. //
  418. //
  419. // This is where you can put code to modify the VMF.
  420. //
  421. //
  422. //
  423. // If they didn't specify -game on the command line, use VPROJECT.
  424. char workingdir[ 256 ];
  425. workingdir[0] = 0;
  426. Q_getwd( workingdir, sizeof(workingdir) );
  427. CmdLib_InitFileSystem( workingdir );
  428. ScanChunks( pRoot, LogicAuto );
  429. FileSystem_Term();
  430. Msg( "%s: %d logic_auto replacements made.\n", g_pInFilename, g_nLogicAutoReplacementsMade );
  431. if ( argc >= 3 )
  432. {
  433. if( !WriteChunkFile( pOutFilename, pRoot ) )
  434. return 3;
  435. }
  436. return 0;
  437. }