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.

1010 lines
28 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "MapReslistGenerator.h"
  7. #include "filesystem.h"
  8. #include "filesystem_engine.h"
  9. #include "sys.h"
  10. #include "cmd.h"
  11. #include "common.h"
  12. #include "quakedef.h"
  13. #include "vengineserver_impl.h"
  14. #include "tier1/strtools.h"
  15. #include "tier0/icommandline.h"
  16. #include <ctype.h>
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <tier0/dbg.h>
  20. #include "host.h"
  21. #include "host_state.h"
  22. #include "utlbuffer.h"
  23. #include "characterset.h"
  24. #include "tier1/fmtstr.h"
  25. // memdbgon must be the last include file in a .cpp file!!!
  26. #include "tier0/memdbgon.h"
  27. #define PAUSE_FRAMES_BETWEEN_MAPS 300
  28. #define PAUSE_TIME_BETWEEN_MAPS 2.0f
  29. extern engineparms_t host_parms;
  30. #define ENGINE_RESLIST_FILE "engine.lst"
  31. //-----------------------------------------------------------------------------
  32. // Purpose:
  33. //-----------------------------------------------------------------------------
  34. void MapReslistGenerator_Usage()
  35. {
  36. Msg( "-makereslists usage:\n" );
  37. Msg( " [ -makereslists <optionalscriptfile> ] -- script file to control more complex makereslists operations (multiple passes, etc.)\n" );
  38. Msg( " [ -usereslistfile filename ] -- get map list from specified file, default is to build for maps/*.bsp\n" );
  39. Msg( " [ -startmap mapname ] -- restart generation at specified map (after crash, implies resume)\n" );
  40. Msg( " [ -condebug ] -- prepend console.log entries with mapname or engine if not in a map\n" );
  41. Msg( " [ +map mapname ] -- generate reslists for specified map and exit after that map\n" );
  42. Msg( " [ -rebuildaudio ] -- force rebuild of _other_rebuild.cache (metacache) file at exit\n" );
  43. Msg( " [ -forever ] -- when you get to the end of the maplist, start over from the top\n" );
  44. Msg( " [ -reslistdir ] -- default is 'reslists', use this to override\n" );
  45. Msg( " [ -startstage nnn ] -- when running from script file, this starts at specified stage\n" );
  46. Msg( " [ -collate ] -- skip everything, just merge the reslist from temp folders to the final folder again\n" );
  47. }
  48. void MapReslistGenerator_Init()
  49. {
  50. // check for reslist generation
  51. if ( CommandLine()->FindParm("-makereslists") )
  52. {
  53. bool usemaplistfile = false;
  54. if ( CommandLine()->FindParm("-usereslistfile") )
  55. {
  56. usemaplistfile = true;
  57. }
  58. MapReslistGenerator().EnableReslistGeneration( usemaplistfile );
  59. }
  60. else if ( CommandLine()->FindParm( "-rebuildaudio" ) )
  61. {
  62. MapReslistGenerator().SetAutoQuit( true );
  63. }
  64. if ( CommandLine()->FindParm( "-trackdeletions" ) )
  65. {
  66. MapReslistGenerator().EnableDeletionsTracking();
  67. }
  68. }
  69. void MapReslistGenerator_Shutdown()
  70. {
  71. MapReslistGenerator().Shutdown();
  72. }
  73. void MapReslistGenerator_BuildMapList()
  74. {
  75. MapReslistGenerator().BuildMapList();
  76. }
  77. CMapReslistGenerator g_MapReslistGenerator;
  78. CMapReslistGenerator &MapReslistGenerator()
  79. {
  80. return g_MapReslistGenerator;
  81. }
  82. static bool ReslistLogLessFunc( CUtlString const &pLHS, CUtlString const &pRHS )
  83. {
  84. return CaselessStringLessThan( pLHS.Get(), pRHS.Get() );
  85. }
  86. //-----------------------------------------------------------------------------
  87. // Purpose: Constructor
  88. //-----------------------------------------------------------------------------
  89. CMapReslistGenerator::CMapReslistGenerator() :
  90. m_AlreadyWrittenFileNames( 0, 0, true ),
  91. m_DeletionListWarnings( 0, 0, DefLessFunc( CUtlSymbol ) ),
  92. m_EngineLog( 0, 0, ReslistLogLessFunc ),
  93. m_MapLog( 0, 0, ReslistLogLessFunc ),
  94. m_bAutoQuit( false )
  95. {
  96. MEM_ALLOC_CREDIT_CLASS();
  97. m_bUsingMapList = false;
  98. m_bTrackingDeletions = false;
  99. m_bLoggingEnabled = false;
  100. m_iCurrentMap = 0;
  101. m_flNextMapRunTime = 0.0f;
  102. m_iFrameCountdownToRunningNextMap = 0;
  103. m_szPrefix[0] = '\0';
  104. m_szLevelName[0] = '\0';
  105. m_iPauseTimeBetweenMaps = PAUSE_TIME_BETWEEN_MAPS;
  106. m_iPauseFramesBetweenMaps = PAUSE_FRAMES_BETWEEN_MAPS;
  107. m_bRestartOnTransition = false;
  108. m_bLogToEngineList = true;
  109. m_sResListDir = "reslists";
  110. }
  111. void CMapReslistGenerator::SetAutoQuit( bool bState )
  112. {
  113. m_bAutoQuit = bState;
  114. }
  115. void CMapReslistGenerator::BuildMapList()
  116. {
  117. if ( !IsEnabled() )
  118. return;
  119. MapReslistGenerator_Usage();
  120. // Get the maplist file, if any
  121. const char *pMapFile = NULL;
  122. CommandLine()->CheckParm( "-usereslistfile", &pMapFile );
  123. // +map argument precludes using a maplist file
  124. bool bUseMap = CommandLine()->FindParm("+map") != 0;
  125. bool bUseMapListFile = bUseMap ? false : CommandLine()->FindParm("-usereslistfile") != 0;
  126. // Build the map list
  127. if ( !BuildGeneralMapList( &m_Maps, bUseMapListFile, pMapFile, "reslists", &m_iCurrentMap ) )
  128. {
  129. m_bLoggingEnabled = false;
  130. }
  131. }
  132. bool BuildGeneralMapList( CUtlVector<maplist_map_t> *aMaps, bool bUseMapListFile, const char *pMapFile, char *pSystemMsg, int *iCurrentMap )
  133. {
  134. if ( !bUseMapListFile )
  135. {
  136. // If the user passed in a +map parameter, just use that single map
  137. char const *pMapName = NULL;
  138. if ( CommandLine()->CheckParm( "+map", &pMapName ) && pMapName )
  139. {
  140. // ensure validity
  141. char szMapFile[64] = { 0 };
  142. V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", pMapName );
  143. if (g_pVEngineServer->IsMapValid( szMapFile ))
  144. {
  145. // add to list
  146. maplist_map_t newMap;
  147. Q_strncpy(newMap.name, pMapName, sizeof(newMap.name));
  148. aMaps->AddToTail( newMap );
  149. }
  150. CommandLine()->RemoveParm( "+map" );
  151. }
  152. else
  153. {
  154. // build the list of all the levels to scan
  155. // Search the directory structure.
  156. const char *mapwild = "maps/*.bsp";
  157. char const *findfn = Sys_FindFirst( mapwild, NULL, 0 );
  158. while ( findfn )
  159. {
  160. // make sure that it's in the mod filesystem
  161. if ( !g_pFileSystem->FileExists( va("maps/%s", findfn), "MOD" ) )
  162. {
  163. findfn = Sys_FindNext( NULL, 0 );
  164. continue;
  165. }
  166. // strip extension
  167. char sz[ MAX_PATH ];
  168. Q_strncpy( sz, findfn, sizeof( sz ) );
  169. char *ext = strchr( sz, '.' );
  170. if (ext)
  171. {
  172. ext[0] = 0;
  173. }
  174. // move to next item
  175. findfn = Sys_FindNext( NULL, 0 );
  176. // ensure validity
  177. char szMapFile[64] = { 0 };
  178. V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", sz );
  179. if (!g_pVEngineServer->IsMapValid( szMapFile ))
  180. continue;
  181. // add to list
  182. maplist_map_t newMap;
  183. Q_strncpy(newMap.name, sz, sizeof(newMap.name));
  184. aMaps->AddToTail( newMap );
  185. }
  186. Sys_FindClose();
  187. }
  188. }
  189. else
  190. {
  191. // Read from file
  192. if ( pMapFile )
  193. {
  194. // Load them in
  195. FileHandle_t resfilehandle;
  196. resfilehandle = g_pFileSystem->Open( pMapFile, "rb" );
  197. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  198. {
  199. // Read in and parse mapcycle.txt
  200. int length = g_pFileSystem->Size(resfilehandle);
  201. if ( length > 0 )
  202. {
  203. char *pStart = (char *)new char[ length + 1 ];
  204. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) )
  205. {
  206. pStart[ length ] = 0;
  207. const char *pFileList = pStart;
  208. while ( 1 )
  209. {
  210. char szMap[ MAX_OSPATH ];
  211. pFileList = COM_Parse( pFileList );
  212. if ( strlen( com_token ) <= 0 )
  213. break;
  214. Q_strncpy(szMap, com_token, sizeof(szMap));
  215. // ensure validity
  216. char szMapFile[64] = { 0 };
  217. V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", szMap );
  218. if (!g_pVEngineServer->IsMapValid( szMapFile ))
  219. continue;
  220. // Any more tokens on this line?
  221. while ( COM_TokenWaiting( pFileList ) )
  222. {
  223. pFileList = COM_Parse( pFileList );
  224. }
  225. maplist_map_t newMap;
  226. Q_strncpy(newMap.name, szMap, sizeof(newMap.name));
  227. aMaps->AddToTail( newMap );
  228. }
  229. }
  230. delete[] pStart;
  231. }
  232. g_pFileSystem->Close(resfilehandle);
  233. }
  234. else
  235. {
  236. Error( "Unable to load %s maplist file: %s\n", pSystemMsg, pMapFile );
  237. return false;
  238. }
  239. }
  240. else
  241. {
  242. Error( "Unable to find %s maplist filename\n", pSystemMsg );
  243. return false;
  244. }
  245. }
  246. int c = aMaps->Count();
  247. if ( c == 0 )
  248. {
  249. Msg( "%s: No maps found\n", pSystemMsg );
  250. return false;
  251. }
  252. Msg( "%s: Creating for:\n", pSystemMsg );
  253. // Determine the current map (-startmap allows starts mid-maplist)
  254. *iCurrentMap = 0;
  255. char const *startmap = NULL;
  256. if ( CommandLine()->CheckParm( "-startmap", &startmap ) && startmap )
  257. {
  258. for ( int i = 0 ; i < c; ++i )
  259. {
  260. if ( !Q_stricmp( aMaps->Element(i).name, startmap ) )
  261. {
  262. *iCurrentMap = i;
  263. }
  264. }
  265. }
  266. for ( int i = 0 ; i < c; ++i )
  267. {
  268. if ( i < *iCurrentMap )
  269. {
  270. Msg( "- %s\n", aMaps->Element(i).name );
  271. }
  272. else
  273. {
  274. Msg( "+ %s\n", aMaps->Element(i).name );
  275. }
  276. }
  277. return true;
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose: Reconstructs engine log dictionary from existing engine reslist.
  281. // This is used to restore state after a restart, otherwise the engine log
  282. // would aggregate duplicate files.
  283. //-----------------------------------------------------------------------------
  284. void CMapReslistGenerator::BuildEngineLogFromReslist()
  285. {
  286. m_EngineLog.RemoveAll();
  287. CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
  288. if ( !g_pFileSystem->ReadFile( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "DEFAULT_WRITE_PATH", buffer ) )
  289. {
  290. // does not exist
  291. return;
  292. }
  293. characterset_t breakSet;
  294. CharacterSetBuild( &breakSet, "" );
  295. // parse reslist
  296. char szToken[MAX_PATH];
  297. for ( ;; )
  298. {
  299. int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) );
  300. if ( nTokenSize <= 0 )
  301. {
  302. break;
  303. }
  304. int idx = m_EngineLog.Find( szToken );
  305. if ( idx == m_EngineLog.InvalidIndex() )
  306. {
  307. m_EngineLog.Insert( szToken );
  308. }
  309. }
  310. }
  311. //-----------------------------------------------------------------------------
  312. // Purpose: Appends specified line to the engine reslist.
  313. //-----------------------------------------------------------------------------
  314. void CMapReslistGenerator::LogToEngineReslist( char const *pLine )
  315. {
  316. // prevent unecessary duplication due to file appending
  317. int idx = m_EngineLog.Find( pLine );
  318. if ( idx != m_EngineLog.InvalidIndex() )
  319. {
  320. // already logged
  321. return;
  322. }
  323. m_EngineLog.Insert( pLine );
  324. // Open for append, write data, close.
  325. FileHandle_t fh = g_pFileSystem->Open( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "at", "DEFAULT_WRITE_PATH" );
  326. if ( fh != FILESYSTEM_INVALID_HANDLE )
  327. {
  328. g_pFileSystem->Write( "\"", 1, fh );
  329. g_pFileSystem->Write( pLine, Q_strlen( pLine ), fh );
  330. g_pFileSystem->Write( "\"\n", 2, fh );
  331. g_pFileSystem->Close( fh );
  332. }
  333. }
  334. //-----------------------------------------------------------------------------
  335. // Purpose: initializes the object to enable reslist generation
  336. //-----------------------------------------------------------------------------
  337. void CMapReslistGenerator::EnableReslistGeneration( bool usemaplistfile )
  338. {
  339. //hackhack !!!! This is a work-around until CS precaches things on level start, not player spawn
  340. if ( !Q_stricmp( "cstrike", GetCurrentMod() ))
  341. {
  342. m_iPauseTimeBetweenMaps = PAUSE_TIME_BETWEEN_MAPS * 3;
  343. m_iPauseFramesBetweenMaps = PAUSE_FRAMES_BETWEEN_MAPS * 3;
  344. }
  345. m_bUsingMapList = usemaplistfile;
  346. m_bLoggingEnabled = true;
  347. char const *pszDir = NULL;
  348. if ( CommandLine()->CheckParm( "-reslistdir", &pszDir ) && pszDir )
  349. {
  350. char szDir[ MAX_PATH ];
  351. Q_strncpy( szDir, pszDir, sizeof( szDir ) );
  352. Q_StripTrailingSlash( szDir );
  353. Q_strlower( szDir );
  354. Q_FixSlashes( szDir );
  355. if ( Q_strlen( szDir ) > 0 )
  356. {
  357. m_sResListDir = szDir;
  358. }
  359. }
  360. // create file to dump out to
  361. g_pFileSystem->CreateDirHierarchy( m_sResListDir.String() , "DEFAULT_WRITE_PATH" );
  362. // Leave the existing one if resuming from a specific map, otherwise, blow it away
  363. if ( !CommandLine()->FindParm( "-startmap" ) )
  364. {
  365. g_pFileSystem->RemoveFile( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "DEFAULT_WRITE_PATH" );
  366. m_EngineLog.RemoveAll();
  367. }
  368. else
  369. {
  370. BuildEngineLogFromReslist();
  371. }
  372. // add logging function
  373. g_pFileSystem->AddLoggingFunc(&FileSystemLoggingFunc);
  374. }
  375. //-----------------------------------------------------------------------------
  376. // Purpose: starts the first map
  377. //-----------------------------------------------------------------------------
  378. void CMapReslistGenerator::StartReslistGeneration()
  379. {
  380. m_iCurrentMap = 0;
  381. m_iFrameCountdownToRunningNextMap = m_iPauseFramesBetweenMaps;
  382. m_flNextMapRunTime = Sys_FloatTime() + m_iPauseTimeBetweenMaps;
  383. }
  384. //-----------------------------------------------------------------------------
  385. // Purpose:
  386. // Input : *mapname -
  387. //-----------------------------------------------------------------------------
  388. void CMapReslistGenerator::SetPrefix( char const *mapname )
  389. {
  390. Q_snprintf( m_szPrefix, sizeof( m_szPrefix ), "%s: ", mapname );
  391. }
  392. //-----------------------------------------------------------------------------
  393. // Purpose:
  394. // Output : char const
  395. //-----------------------------------------------------------------------------
  396. char const *CMapReslistGenerator::LogPrefix()
  397. {
  398. // If not recording stuff to file, then use the "default" prefix.
  399. if ( m_bLogToEngineList )
  400. {
  401. return "engine: ";
  402. }
  403. return m_szPrefix;
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose: call to mark level load/end
  407. //-----------------------------------------------------------------------------
  408. void CMapReslistGenerator::OnLevelLoadStart(const char *levelName)
  409. {
  410. if ( !IsEnabled() )
  411. return;
  412. // reset the duplication list
  413. m_AlreadyWrittenFileNames.RemoveAll();
  414. // prepare for map logging
  415. m_bLogToEngineList = false;
  416. m_MapLog.RemoveAll();
  417. V_strncpy( m_szLevelName, levelName, sizeof( m_szLevelName ) );
  418. // add in the bsp file to the list, and its node graph
  419. char path[MAX_PATH];
  420. Q_snprintf( path, sizeof( path ), "maps\\%s.bsp", levelName );
  421. OnResourcePrecached( path );
  422. bool useNodeGraph = true;
  423. KeyValues *modinfo = new KeyValues("ModInfo");
  424. if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
  425. {
  426. useNodeGraph = modinfo->GetInt( "nodegraph", 1 ) != 0;
  427. }
  428. modinfo->deleteThis();
  429. if ( useNodeGraph )
  430. {
  431. Q_snprintf(path, sizeof(path), "maps\\graphs\\%s.ain", levelName);
  432. OnResourcePrecached(path);
  433. }
  434. }
  435. //-----------------------------------------------------------------------------
  436. // Purpose: call to mark level load/end
  437. //-----------------------------------------------------------------------------
  438. void CMapReslistGenerator::OnLevelLoadEnd()
  439. {
  440. }
  441. void CMapReslistGenerator::OnPlayerSpawn()
  442. {
  443. if ( !IsEnabled() )
  444. return;
  445. // initiate the next level
  446. m_iFrameCountdownToRunningNextMap = m_iPauseFramesBetweenMaps;
  447. m_flNextMapRunTime = Sys_FloatTime() + m_iPauseTimeBetweenMaps;
  448. }
  449. bool CMapReslistGenerator::ShouldRebuildCaches()
  450. {
  451. if ( !IsEnabled() )
  452. {
  453. return CommandLine()->FindParm( "-rebuildaudio" ) != 0;
  454. }
  455. if ( !CommandLine()->FindParm( "-norebuildaudio" ) )
  456. return true;
  457. return false;
  458. }
  459. char const *CMapReslistGenerator::GetResListDirectory() const
  460. {
  461. return m_sResListDir.String();
  462. }
  463. void CMapReslistGenerator::DoQuit()
  464. {
  465. Cbuf_AddText( "quit\n" );
  466. // remove the logging
  467. g_pFileSystem->RemoveLoggingFunc(&FileSystemLoggingFunc);
  468. m_bLogToEngineList = true;
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Purpose: call every frame if we're enabled, just so that the next map can be triggered at the right time
  472. //-----------------------------------------------------------------------------
  473. void CMapReslistGenerator::RunFrame()
  474. {
  475. if ( !IsEnabled() )
  476. {
  477. if ( m_bAutoQuit )
  478. {
  479. m_bAutoQuit = false;
  480. DoQuit();
  481. }
  482. return;
  483. }
  484. if ( --m_iFrameCountdownToRunningNextMap > 0 )
  485. return;
  486. if ( m_flNextMapRunTime && m_flNextMapRunTime < Sys_FloatTime() )
  487. {
  488. // about to transition or terminate, emit the current map log
  489. WriteMapLog();
  490. if ( m_Maps.IsValidIndex( m_iCurrentMap ) )
  491. {
  492. m_flNextMapRunTime = 0.0f;
  493. m_iFrameCountdownToRunningNextMap = 0;
  494. if ( !m_bRestartOnTransition )
  495. {
  496. Cbuf_AddText( va( "map %s\n", m_Maps[m_iCurrentMap].name ) );
  497. SetPrefix( m_Maps[m_iCurrentMap].name );
  498. ++m_iCurrentMap;
  499. if ( m_Maps.IsValidIndex( m_iCurrentMap ) )
  500. {
  501. // cause a full engine restart on the transition to the next map
  502. // ensure that one-time init code logs correctly to each map reslist
  503. m_bRestartOnTransition = true;
  504. }
  505. }
  506. else
  507. {
  508. // restart at specified map
  509. CommandLine()->RemoveParm( "-startmap" );
  510. CommandLine()->AppendParm( "-startmap", m_Maps[m_iCurrentMap].name );
  511. HostState_Restart();
  512. }
  513. }
  514. else
  515. {
  516. // no more levels, just quit
  517. if ( !CommandLine()->FindParm( "-forever" ) )
  518. {
  519. DoQuit();
  520. }
  521. else
  522. {
  523. StartReslistGeneration();
  524. m_bRestartOnTransition = true;
  525. }
  526. }
  527. }
  528. }
  529. //-----------------------------------------------------------------------------
  530. // Purpose: logs and handles mdl files being precaches
  531. //-----------------------------------------------------------------------------
  532. void CMapReslistGenerator::OnModelPrecached(const char *relativePathFileName)
  533. {
  534. if ( !IsEnabled() )
  535. return;
  536. if (strstr(relativePathFileName, ".vmt"))
  537. {
  538. // it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
  539. char file[_MAX_PATH];
  540. if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
  541. {
  542. Q_strncpy(file, relativePathFileName, sizeof(file));
  543. }
  544. else
  545. {
  546. // prepend the materials directory
  547. Q_snprintf(file, sizeof(file), "materials\\%s", relativePathFileName);
  548. }
  549. OnResourcePrecached(file);
  550. // get the matching vtf file
  551. char *ext = strstr(file, ".vmt");
  552. if (ext)
  553. {
  554. Q_strncpy(ext, ".vtf", 5);
  555. OnResourcePrecached(file);
  556. }
  557. }
  558. else
  559. {
  560. OnResourcePrecached(relativePathFileName);
  561. }
  562. }
  563. //-----------------------------------------------------------------------------
  564. // Purpose: logs sound file access
  565. //-----------------------------------------------------------------------------
  566. void CMapReslistGenerator::OnSoundPrecached(const char *relativePathFileName)
  567. {
  568. // skip any special characters
  569. if (!V_isalnum(relativePathFileName[0]))
  570. {
  571. ++relativePathFileName;
  572. }
  573. // prepend the sound/ directory if necessary
  574. char file[_MAX_PATH];
  575. if (!Q_strnicmp(relativePathFileName, "sound", strlen("sound")))
  576. {
  577. Q_strncpy(file, relativePathFileName, sizeof(file));
  578. }
  579. else
  580. {
  581. // prepend the sound directory
  582. Q_snprintf(file, sizeof(file), "sound\\%s", relativePathFileName);
  583. }
  584. OnResourcePrecached(file);
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose: logs the precache as a file access
  588. //-----------------------------------------------------------------------------
  589. void CMapReslistGenerator::OnResourcePrecached(const char *relativePathFileName)
  590. {
  591. if ( !IsEnabled() )
  592. return;
  593. // ignore empty string
  594. if (relativePathFileName[0] == 0)
  595. return;
  596. // ignore files that start with '*' since they signify special models
  597. if (relativePathFileName[0] == '*')
  598. return;
  599. char fullPath[_MAX_PATH];
  600. if (g_pFileSystem->GetLocalPath(relativePathFileName, fullPath, sizeof(fullPath)))
  601. {
  602. OnResourcePrecachedFullPath(fullPath);
  603. }
  604. }
  605. //-----------------------------------------------------------------------------
  606. // Purpose: Logs out file access to a file
  607. //-----------------------------------------------------------------------------
  608. void CMapReslistGenerator::OnResourcePrecachedFullPath(const char *fullPathFileName)
  609. {
  610. char fixed[ MAX_PATH ];
  611. Q_strncpy( fixed, fullPathFileName, sizeof( fixed ) );
  612. Q_strlower( fixed );
  613. Q_FixSlashes( fixed );
  614. // make sure the filename hasn't already been written
  615. UtlSymId_t filename = m_AlreadyWrittenFileNames.Find( fixed );
  616. if ( filename != UTL_INVAL_SYMBOL )
  617. return;
  618. // record in list, so we don't write it again
  619. m_AlreadyWrittenFileNames.AddString( fixed );
  620. // add extras for mdl's
  621. if (strstr(fixed, ".mdl"))
  622. {
  623. // it's a model, get it's other files as well
  624. char file[_MAX_PATH];
  625. Q_strncpy(file, fixed, sizeof(file) - 10);
  626. char *ext = strstr(file, ".mdl");
  627. Q_strncpy(ext, ".vvd", 10);
  628. OnResourcePrecachedFullPath(file);
  629. Q_strncpy(ext, ".ani", 10);
  630. OnResourcePrecachedFullPath(file);
  631. Q_strncpy(ext, ".dx80.vtx", 10);
  632. OnResourcePrecachedFullPath(file);
  633. Q_strncpy(ext, ".dx90.vtx", 10);
  634. OnResourcePrecachedFullPath(file);
  635. Q_strncpy(ext, ".sw.vtx", 10);
  636. OnResourcePrecachedFullPath(file);
  637. Q_strncpy(ext, ".phy", 10);
  638. OnResourcePrecachedFullPath(file);
  639. Q_strncpy(ext, ".jpg", 10);
  640. OnResourcePrecachedFullPath(file);
  641. }
  642. // strip it down relative to the root directory of the game (for steam)
  643. char const *relativeFileName = Q_stristr( fixed, GetBaseDirectory() );
  644. if ( relativeFileName )
  645. {
  646. // Skip the basedir and slash
  647. relativeFileName += ( Q_strlen( GetBaseDirectory() ) + 1 );
  648. }
  649. if ( !relativeFileName )
  650. {
  651. return;
  652. }
  653. if ( m_bLogToEngineList )
  654. {
  655. LogToEngineReslist( relativeFileName );
  656. }
  657. else
  658. {
  659. // find or add to sorted tree
  660. int idx = m_MapLog.Find( relativeFileName );
  661. if ( idx == m_MapLog.InvalidIndex() )
  662. {
  663. m_MapLog.Insert( relativeFileName );
  664. }
  665. }
  666. }
  667. void CMapReslistGenerator::WriteMapLog()
  668. {
  669. if ( !m_szLevelName[0] )
  670. {
  671. // log has not been established yet
  672. return;
  673. }
  674. // write the sorted map log, allows for easier diffs between revisions
  675. char path[_MAX_PATH];
  676. Q_snprintf( path, sizeof( path ), "%s\\%s.lst", m_sResListDir.String(), m_szLevelName );
  677. FileHandle_t fh = g_pFileSystem->Open( path, "wt", "DEFAULT_WRITE_PATH" );
  678. for ( int i = m_MapLog.FirstInorder(); i != m_MapLog.InvalidIndex(); i = m_MapLog.NextInorder( i ) )
  679. {
  680. const char *pLine = m_MapLog[i].String();
  681. g_pFileSystem->Write( "\"", 1, fh );
  682. g_pFileSystem->Write( pLine, Q_strlen( pLine ), fh );
  683. g_pFileSystem->Write( "\"\n", 2, fh );
  684. }
  685. g_pFileSystem->Close( fh );
  686. }
  687. //-----------------------------------------------------------------------------
  688. // Purpose: callback function from filesystem
  689. //-----------------------------------------------------------------------------
  690. void CMapReslistGenerator::FileSystemLoggingFunc(const char *fullPathFileName, const char *options)
  691. {
  692. g_MapReslistGenerator.OnResourcePrecachedFullPath(fullPathFileName);
  693. }
  694. #define DELETIONS_BATCH_FILE "deletions.bat"
  695. #define DELETIONS_WARNINGS_FILE "undelete.lst"
  696. //-----------------------------------------------------------------------------
  697. // Purpose:
  698. //-----------------------------------------------------------------------------
  699. void CMapReslistGenerator::EnableDeletionsTracking()
  700. {
  701. unsigned int deletions = 0;
  702. unsigned int warnings = 0;
  703. // Load deletions file and build dictionary
  704. m_bTrackingDeletions = true;;
  705. // Open up deletions.bat and parse out all filenames
  706. // Load them in
  707. FileHandle_t deletionsfile;
  708. deletionsfile = g_pFileSystem->Open( DELETIONS_BATCH_FILE, "rb" );
  709. if ( FILESYSTEM_INVALID_HANDLE != deletionsfile )
  710. {
  711. // Read in and parse mapcycle.txt
  712. int length = g_pFileSystem->Size(deletionsfile);
  713. if ( length > 0 )
  714. {
  715. char *pStart = (char *)new char[ length + 1 ];
  716. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, deletionsfile) ) )
  717. {
  718. pStart[ length ] = 0;
  719. const char *pFileList = pStart;
  720. while ( 1 )
  721. {
  722. char filename[ MAX_OSPATH ];
  723. pFileList = COM_Parse( pFileList );
  724. if ( strlen( com_token ) <= 0 )
  725. break;
  726. if ( !Q_stricmp( com_token, "del" ) )
  727. continue;
  728. Q_snprintf(filename, sizeof( filename ), "%s/%s", com_gamedir, com_token );
  729. // Any more tokens on this line?
  730. while ( COM_TokenWaiting( pFileList ) )
  731. {
  732. pFileList = COM_Parse( pFileList );
  733. }
  734. Q_FixSlashes( filename );
  735. Q_strlower( filename );
  736. m_DeletionList.AddString( filename );
  737. ++deletions;
  738. }
  739. }
  740. delete[] pStart;
  741. }
  742. g_pFileSystem->Close(deletionsfile);
  743. }
  744. else
  745. {
  746. Warning( "Unable to load deletions.bat file %s\n", DELETIONS_BATCH_FILE );
  747. m_bTrackingDeletions = false;
  748. return;
  749. }
  750. FileHandle_t warningsfile = g_pFileSystem->Open( DELETIONS_WARNINGS_FILE, "rb" );
  751. if ( FILESYSTEM_INVALID_HANDLE != warningsfile )
  752. {
  753. // Read in and parse mapcycle.txt
  754. int length = g_pFileSystem->Size(warningsfile);
  755. if ( length > 0 )
  756. {
  757. char *pStart = (char *)new char[ length + 1 ];
  758. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, warningsfile) ) )
  759. {
  760. pStart[ length ] = 0;
  761. const char *pFileList = pStart;
  762. while ( 1 )
  763. {
  764. pFileList = COM_Parse( pFileList );
  765. if ( strlen( com_token ) <= 0 )
  766. break;
  767. Q_FixSlashes( com_token );
  768. Q_strlower( com_token );
  769. CUtlSymbol sym = m_DeletionListWarningsSymbols.AddString( com_token );
  770. int idx = m_DeletionListWarnings.Find( sym );
  771. if ( idx == m_DeletionListWarnings.InvalidIndex() )
  772. {
  773. m_DeletionListWarnings.Insert( sym );
  774. ++warnings;
  775. }
  776. }
  777. }
  778. delete[] pStart;
  779. }
  780. g_pFileSystem->Close(warningsfile);
  781. }
  782. // Hook up logging function
  783. g_pFileSystem->AddLoggingFunc( &TrackDeletionsLoggingFunc );
  784. Msg( "Tracking deletions (%u files in deletion list in '%s', %u previous warnings loaded from '%s'\n",
  785. deletions, DELETIONS_BATCH_FILE, warnings, DELETIONS_WARNINGS_FILE );
  786. }
  787. //-----------------------------------------------------------------------------
  788. // Purpose:
  789. // Input : *fullPathFileName -
  790. //-----------------------------------------------------------------------------
  791. void CMapReslistGenerator::TrackDeletions( const char *fullPathFileName )
  792. {
  793. Assert( m_bTrackingDeletions );
  794. char test[ _MAX_PATH ];
  795. Q_strncpy( test, fullPathFileName, sizeof( test ) );
  796. Q_FixSlashes( test );
  797. Q_strlower( test );
  798. CUtlSymbol sym = m_DeletionList.Find( test );
  799. if ( UTL_INVAL_SYMBOL != sym )
  800. {
  801. CUtlSymbol warningSymbol = m_DeletionListWarningsSymbols.AddString( test );
  802. uint idx = m_DeletionListWarnings.Find( warningSymbol );
  803. if ( idx == m_DeletionListWarnings.InvalidIndex() )
  804. {
  805. Msg( "--> Referenced file marked for deletion \"%s\"\n", test );
  806. m_DeletionListWarnings.Insert( warningSymbol );
  807. }
  808. }
  809. // add extras for mdl's
  810. if (strstr(test, ".mdl"))
  811. {
  812. // it's a model, get it's other files as well
  813. char file[_MAX_PATH];
  814. Q_strncpy(file, test, sizeof(file) - 10);
  815. char *ext = strstr(file, ".mdl");
  816. Q_strncpy(ext, ".vvd", 10);
  817. TrackDeletions(file);
  818. Q_strncpy(ext, ".ani", 10);
  819. TrackDeletions(file);
  820. Q_strncpy(ext, ".dx80.vtx", 10);
  821. TrackDeletions(file);
  822. Q_strncpy(ext, ".dx90.vtx", 10);
  823. TrackDeletions(file);
  824. Q_strncpy(ext, ".sw.vtx", 10);
  825. TrackDeletions(file);
  826. Q_strncpy(ext, ".phy", 10);
  827. TrackDeletions(file);
  828. Q_strncpy(ext, ".jpg", 10);
  829. TrackDeletions(file);
  830. }
  831. }
  832. //-----------------------------------------------------------------------------
  833. // Purpose: callback function from filesystem
  834. //-----------------------------------------------------------------------------
  835. void CMapReslistGenerator::TrackDeletionsLoggingFunc(const char *fullPathFileName, const char *options)
  836. {
  837. g_MapReslistGenerator.TrackDeletions(fullPathFileName);
  838. }
  839. //-----------------------------------------------------------------------------
  840. // Purpose:
  841. //-----------------------------------------------------------------------------
  842. void CMapReslistGenerator::Shutdown()
  843. {
  844. if ( m_bTrackingDeletions )
  845. {
  846. SpewTrackedDeletionsLog();
  847. g_pFileSystem->RemoveLoggingFunc( &TrackDeletionsLoggingFunc );
  848. m_DeletionList.RemoveAll();
  849. m_DeletionListWarnings.RemoveAll();
  850. m_DeletionListWarningsSymbols.RemoveAll();
  851. m_bTrackingDeletions = NULL;
  852. }
  853. }
  854. void CMapReslistGenerator::SpewTrackedDeletionsLog()
  855. {
  856. if ( !m_bTrackingDeletions )
  857. return;
  858. FileHandle_t hUndeleteFile = g_pFileSystem->Open( DELETIONS_WARNINGS_FILE, "wt", "DEFAULT_WRITE_PATH" );
  859. if ( FILESYSTEM_INVALID_HANDLE == hUndeleteFile )
  860. {
  861. return;
  862. }
  863. for ( int i = m_DeletionListWarnings.FirstInorder(); i != m_DeletionListWarnings.InvalidIndex() ; i = m_DeletionListWarnings.NextInorder( i ) )
  864. {
  865. char const *filename = m_DeletionListWarningsSymbols.String( m_DeletionListWarnings[ i ] );
  866. g_pFileSystem->Write("\"", 1, hUndeleteFile);
  867. g_pFileSystem->Write(filename, Q_strlen(filename), hUndeleteFile);
  868. g_pFileSystem->Write("\"\n", 2, hUndeleteFile);
  869. }
  870. g_pFileSystem->Close( hUndeleteFile );
  871. }