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.

417 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // The copyright to the contents herein is the property of Valve, L.L.C.
  4. // The contents may be used and/or copied only with the written permission of
  5. // Valve, L.L.C., or in accordance with the terms and conditions stipulated in
  6. // the agreement/contract under which the contents have been supplied.
  7. //
  8. // $Header: $
  9. // $NoKeywords: $
  10. //
  11. //=============================================================================
  12. // Valve includes
  13. #include "appframework/tier2app.h"
  14. #include "filesystem.h"
  15. #include "icommandline.h"
  16. #include "tier2/p4helpers.h"
  17. #include "p4lib/ip4.h"
  18. #include "tier1/KeyValues.h"
  19. #include "tier1/utlbuffer.h"
  20. #include "bsplib.h"
  21. #include "lumpfiles.h"
  22. #include "filesystem_tools.h"
  23. #include "cmdlib.h"
  24. #ifdef _DEBUG
  25. #include <windows.h>
  26. #undef GetCurrentDirectory
  27. #endif
  28. //-----------------------------------------------------------------------------
  29. // Standard spew functions
  30. //-----------------------------------------------------------------------------
  31. static SpewRetval_t SpewStdout( SpewType_t spewType, char const *pMsg )
  32. {
  33. if ( !pMsg )
  34. return SPEW_CONTINUE;
  35. #ifdef _DEBUG
  36. OutputDebugString( pMsg );
  37. #endif
  38. printf( pMsg );
  39. fflush( stdout );
  40. return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE;
  41. }
  42. //-----------------------------------------------------------------------------
  43. // The application object
  44. //-----------------------------------------------------------------------------
  45. class CMkEntityPatchApp : public CTier2SteamApp
  46. {
  47. typedef CTier2SteamApp BaseClass;
  48. public:
  49. // Methods of IApplication
  50. virtual bool Create();
  51. virtual bool PreInit( );
  52. virtual int Main();
  53. virtual void Destroy() {}
  54. void PrintHelp( );
  55. private:
  56. };
  57. DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CMkEntityPatchApp );
  58. //-----------------------------------------------------------------------------
  59. // The application object
  60. //-----------------------------------------------------------------------------
  61. bool CMkEntityPatchApp::Create()
  62. {
  63. SpewOutputFunc( SpewStdout );
  64. AppSystemInfo_t appSystems[] =
  65. {
  66. { "p4lib.dll", P4_INTERFACE_VERSION },
  67. { "", "" } // Required to terminate the list
  68. };
  69. return AddSystems( appSystems );
  70. }
  71. //-----------------------------------------------------------------------------
  72. //
  73. //-----------------------------------------------------------------------------
  74. bool CMkEntityPatchApp::PreInit( )
  75. {
  76. MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
  77. if ( !BaseClass::PreInit() )
  78. return false;
  79. if ( !g_pFullFileSystem )
  80. {
  81. Error( "// ERROR: sfmgen is missing a required interface!\n" );
  82. return false;
  83. }
  84. // Add paths...
  85. if ( !SetupSearchPaths( NULL, false, true ) )
  86. return false;
  87. return true;
  88. }
  89. //-----------------------------------------------------------------------------
  90. // Print help
  91. //-----------------------------------------------------------------------------
  92. void CMkEntityPatchApp::PrintHelp( )
  93. {
  94. Msg( "Usage: mkentitypatch [-nop4] [-vproject <path to gameinfo.txt>] <in .bsp file>\n" );
  95. Msg( "\t-nop4\t: [Optional] Disables auto perforce checkout/add.\n" );
  96. Msg( "\t-vproject\t: [Optional] Specifies path to a gameinfo.txt file (which mod to build for).\n" );
  97. Msg( "\t Source .BSP file whose entity lump you wish to patch.\n" );
  98. }
  99. //-----------------------------------------------------------------------------
  100. // Computes a full directory
  101. //-----------------------------------------------------------------------------
  102. static void ComputeFullPath( const char *pRelativeDir, char *pFullPath, int nBufLen )
  103. {
  104. if ( !Q_IsAbsolutePath( pRelativeDir ) )
  105. {
  106. char pDir[MAX_PATH];
  107. if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
  108. {
  109. Q_ComposeFileName( pDir, pRelativeDir, pFullPath, nBufLen );
  110. }
  111. }
  112. else
  113. {
  114. Q_strncpy( pFullPath, pRelativeDir, nBufLen );
  115. }
  116. Q_StripTrailingSlash( pFullPath );
  117. // Ensure the output directory exists
  118. g_pFullFileSystem->CreateDirHierarchy( pFullPath );
  119. }
  120. //-----------------------------------------------------------------------------
  121. // The application object
  122. //-----------------------------------------------------------------------------
  123. entity_t *FindEntity( KeyValues *pEntity )
  124. {
  125. int nHammerId = pEntity->GetInt( "id", INT_MIN );
  126. if ( nHammerId != INT_MIN )
  127. {
  128. // First, look for hammerid
  129. for ( int i = 0; i < num_entities; ++i )
  130. {
  131. int nId = IntForKeyWithDefault( &entities[i], "hammerid", INT_MIN );
  132. if ( nId == nHammerId )
  133. return &entities[i];
  134. }
  135. }
  136. // Unfortunately, hammmerid appears to be a relatively new feature. Now, we must
  137. // look for target name
  138. int nMatch = -1;
  139. const char *pTargetName = pEntity->GetString( "targetname" );
  140. if ( pTargetName && pTargetName[0] )
  141. {
  142. // First, look for hammerid
  143. for ( int i = 0; i < num_entities; ++i )
  144. {
  145. const char *pMatchTargetName = ValueForKey( &entities[i], "targetname" );
  146. if ( !pMatchTargetName || !pMatchTargetName[0] )
  147. continue;
  148. if ( !V_stricmp( pTargetName, pMatchTargetName ) )
  149. {
  150. if ( nMatch >= 0 )
  151. {
  152. //Warning( "Encountered multiple entities that matched targetname %s!\n", pTargetName );
  153. //return false;
  154. nMatch = -1; // force a fallback to scanning classname and origin
  155. break;
  156. }
  157. else
  158. nMatch = i;
  159. }
  160. }
  161. }
  162. if ( nMatch >= 0 )
  163. return &entities[nMatch];
  164. // No target name? Well, let's try classname and origin.
  165. const char *pClassName = pEntity->GetString( "classname" );
  166. if ( pClassName && pClassName[0] )
  167. {
  168. // First, look for hammerid
  169. for ( int i = 0; i < num_entities; ++i )
  170. {
  171. const char *pMatchClassName = ValueForKey( &entities[i], "classname" );
  172. if ( !pMatchClassName || !pMatchClassName[0] )
  173. continue;
  174. if ( V_stricmp( pClassName, pMatchClassName ) )
  175. continue;
  176. const char *pOrigin = "(na)";
  177. if ( V_stricmp( pClassName, "worldspawn" ) ) // allow worldspawn to match all
  178. {
  179. pOrigin = pEntity->GetString( "origin" );
  180. const char *pMatchOrigin = ValueForKey( &entities[i], "origin" );
  181. if ( !pMatchOrigin || !pMatchOrigin[0] )
  182. continue;
  183. if ( V_stricmp( pOrigin, pMatchOrigin ) )
  184. continue;
  185. }
  186. if ( nMatch >= 0 )
  187. {
  188. Warning( "Encountered multiple entities that matched classname %s, origin %s!\n", pClassName, pOrigin );
  189. return false;
  190. }
  191. nMatch = i;
  192. }
  193. }
  194. if ( nMatch >= 0 )
  195. return &entities[nMatch];
  196. return NULL;
  197. }
  198. bool InsertEntity( entity_t *pEntity, KeyValues *pEntityKeys )
  199. {
  200. CUtlVector<KeyValues *> vecKVs;
  201. for ( KeyValues *pKey = pEntityKeys->GetFirstValue(); pKey; pKey = pKey->GetNextValue() )
  202. {
  203. vecKVs.AddToTail( pKey );
  204. }
  205. FOR_EACH_VEC_BACK( vecKVs, i )
  206. {
  207. epair_t *e = (epair_t*)malloc( sizeof(epair_t) );
  208. memset (e, 0, sizeof(epair_t));
  209. const char *pName = vecKVs[i]->GetName();
  210. if ( strlen(pName) >= MAX_KEY-1 )
  211. {
  212. Warning( "ParseEpar: token %s too long", pName );
  213. return false;
  214. }
  215. e->key = copystring(pName);
  216. const char *pValue = vecKVs[i]->GetString();
  217. if ( strlen(pValue) >= MAX_VALUE-1 )
  218. {
  219. Warning( "ParseEpar: token %s too long", pValue );
  220. return false;
  221. }
  222. e->value = copystring(pValue);
  223. // strip trailing spaces
  224. StripTrailing( e->key );
  225. StripTrailing( e->value );
  226. e->next = pEntity->epairs;
  227. pEntity->epairs = e;
  228. }
  229. // Flatten everything ( specifically, 'connection' keys, necessary to
  230. // make the patch file have the same format as the commentary files )
  231. for ( KeyValues *pKey = pEntityKeys->GetFirstTrueSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() )
  232. {
  233. InsertEntity( pEntity, pKey );
  234. }
  235. return true;
  236. }
  237. bool InsertEntity( KeyValues *pEntity )
  238. {
  239. entity_t &entity = entities[ num_entities++ ];
  240. return InsertEntity( &entity, pEntity );
  241. }
  242. bool ReplaceEntity( KeyValues *pEntity )
  243. {
  244. entity_t *pReplace = FindEntity( pEntity );
  245. if ( !pReplace )
  246. {
  247. Warning( "Tried to replace an entity %s, origin %s, but couldn't find the original!\n", pEntity->GetString( "classname" ), pEntity->GetString( "origin" ) );
  248. return false;
  249. }
  250. epair_t *pNext;
  251. for ( epair_t *e = pReplace->epairs; e; e = pNext )
  252. {
  253. pNext = e->next;
  254. free( e->key );
  255. free( e->value );
  256. free( e );
  257. }
  258. pReplace->epairs = NULL;
  259. return InsertEntity( pReplace, pEntity );
  260. }
  261. //-----------------------------------------------------------------------------
  262. // The application object
  263. //-----------------------------------------------------------------------------
  264. int CMkEntityPatchApp::Main()
  265. {
  266. // Backward compat for bsplib
  267. g_pFileSystem = g_pFullFileSystem;
  268. // This bit of hackery allows us to access files on the harddrive
  269. g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
  270. if ( CommandLine()->CheckParm( "-h" ) || CommandLine()->CheckParm( "-help" ) || CommandLine()->ParmCount() == 1 )
  271. {
  272. PrintHelp();
  273. return 0;
  274. }
  275. // The file name is the last argument
  276. const char *pBSPFile = CommandLine()->GetParm( CommandLine()->ParmCount() - 1 );
  277. if ( !pBSPFile || pBSPFile[0] == 0 || pBSPFile[0] == '-' )
  278. {
  279. PrintHelp();
  280. return 0;
  281. }
  282. char pFullPath[MAX_PATH];
  283. ComputeFullPath( pBSPFile, pFullPath, sizeof(pFullPath) );
  284. char pBSPFileName[MAX_PATH];
  285. char pPatchFileName[MAX_PATH];
  286. char pOutputFileName[MAX_PATH];
  287. V_strcpy( pBSPFileName, pFullPath );
  288. V_strcpy( pPatchFileName, pFullPath );
  289. V_SetExtension( pBSPFileName, ".bsp", sizeof(pBSPFileName) );
  290. V_SetExtension( pPatchFileName, ".pat", sizeof(pPatchFileName) );
  291. GenerateLumpFileName( pFullPath, pOutputFileName, sizeof(pOutputFileName), LUMP_ENTITIES );
  292. if ( !g_pFullFileSystem->FileExists( pBSPFileName ) )
  293. {
  294. Warning( "BSP file %s doesn't exist!\n", pBSPFileName );
  295. return 0;
  296. }
  297. if ( !g_pFullFileSystem->FileExists( pPatchFileName ) )
  298. {
  299. Warning( "BSP patch file %s doesn't exist!\n", pPatchFileName );
  300. return 0;
  301. }
  302. KeyValues *pKeyValues = new KeyValues( "patch" );
  303. if ( !pKeyValues->LoadFromFile( g_pFullFileSystem, pPatchFileName ) )
  304. {
  305. Warning( "Error parsing patch file %s!\n", pPatchFileName );
  306. return 0;
  307. }
  308. LoadBSPFile( pFullPath );
  309. ParseEntities();
  310. for( int i = 0; i < num_entities; i++ )
  311. {
  312. entity_t *pCur = &entities[i];
  313. epair_t *pNext = NULL;
  314. epair_t *pPrev = NULL;
  315. for ( epair_t *e = pCur->epairs; e; e = pNext )
  316. {
  317. pNext = e->next;
  318. e->next = pPrev;
  319. pPrev = e;
  320. }
  321. pCur->epairs = pPrev;
  322. }
  323. for ( KeyValues *pKey = pKeyValues->GetFirstTrueSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() )
  324. {
  325. const char *pKeyName = pKey->GetName();
  326. if ( !V_stricmp( pKeyName, "entity" ) )
  327. {
  328. if ( !InsertEntity( pKey ) )
  329. return 0;
  330. }
  331. else if ( !V_stricmp( pKeyName, "replace_entity" ) )
  332. {
  333. if ( !ReplaceEntity( pKey ) )
  334. return 0;
  335. }
  336. }
  337. // Do Perforce Stuff
  338. if ( CommandLine()->FindParm( "-nop4" ) )
  339. {
  340. g_p4factory->SetDummyMode( true );
  341. }
  342. g_p4factory->SetOpenFileChangeList( "Entity Patch Files" );
  343. CP4AutoAddFile p4AddBSP( pBSPFileName );
  344. CP4AutoAddFile p4AddPatch( pPatchFileName );
  345. CP4AutoEditAddFile p4AddOutput( pOutputFileName );
  346. UnparseEntities();
  347. WriteLumpToFile( pBSPFileName, LUMP_ENTITIES, 0, dentdata.Base(), dentdata.Count() );
  348. pKeyValues->deleteThis();
  349. return -1;
  350. }