Counter Strike : Global Offensive Source Code
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.

1036 lines
28 KiB

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