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.

523 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // Defines the entry point for the application.
  6. //
  7. //===========================================================================//
  8. #include "reslistgenerator.h"
  9. #include "filesystem.h"
  10. #include "tier1/utlrbtree.h"
  11. #include "tier1/fmtstr.h"
  12. #include "characterset.h"
  13. #include "tier1/utlstring.h"
  14. #include "tier1/utlvector.h"
  15. #include "tier1/utlbuffer.h"
  16. #include "tier0/icommandline.h"
  17. #include "tier1/KeyValues.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. bool SaveResList( const CUtlRBTree< CUtlString, int > &list, char const *pchFileName, char const *pchSearchPath )
  21. {
  22. FileHandle_t fh = g_pFullFileSystem->Open( pchFileName, "wt", pchSearchPath );
  23. if ( fh != FILESYSTEM_INVALID_HANDLE )
  24. {
  25. for ( int i = list.FirstInorder(); i != list.InvalidIndex(); i = list.NextInorder( i ) )
  26. {
  27. g_pFullFileSystem->Write( list[ i ].String(), Q_strlen( list[ i ].String() ), fh );
  28. g_pFullFileSystem->Write( "\n", 1, fh );
  29. }
  30. g_pFullFileSystem->Close( fh );
  31. return true;
  32. }
  33. return false;
  34. }
  35. void LoadResList( CUtlRBTree< CUtlString, int > &list, char const *pchFileName, char const *pchSearchPath )
  36. {
  37. CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
  38. if ( !g_pFullFileSystem->ReadFile( pchFileName, pchSearchPath, buffer ) )
  39. {
  40. // does not exist
  41. return;
  42. }
  43. characterset_t breakSet;
  44. CharacterSetBuild( &breakSet, "" );
  45. // parse reslist
  46. char szToken[ MAX_PATH ];
  47. for ( ;; )
  48. {
  49. int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) );
  50. if ( nTokenSize <= 0 )
  51. {
  52. break;
  53. }
  54. Q_strlower( szToken );
  55. Q_FixSlashes( szToken );
  56. // Ensure filename has "quotes" around it
  57. CUtlString s;
  58. if ( szToken[ 0 ] == '\"' )
  59. {
  60. Assert( Q_strlen( szToken ) > 2 );
  61. Assert( szToken[ Q_strlen( szToken ) - 1 ] == '\"' );
  62. s = szToken;
  63. }
  64. else
  65. {
  66. s = CFmtStr( "\"%s\"", szToken );
  67. }
  68. int idx = list.Find( s );
  69. if ( idx == list.InvalidIndex() )
  70. {
  71. list.Insert( s );
  72. }
  73. }
  74. }
  75. static bool ReslistLogLessFunc( CUtlString const &pLHS, CUtlString const &pRHS )
  76. {
  77. return CaselessStringLessThan( pLHS.Get(), pRHS.Get() );
  78. }
  79. void SortResList( char const *pchFileName, char const *pchSearchPath )
  80. {
  81. CUtlRBTree< CUtlString, int > sorted( 0, 0, ReslistLogLessFunc );
  82. LoadResList( sorted, pchFileName, pchSearchPath );
  83. // Now write it back out
  84. SaveResList( sorted, pchFileName, pchSearchPath );
  85. }
  86. void MergeResLists( CUtlVector< CUtlString > &fileNames, char const *pchOutputFile, char const *pchSearchPath )
  87. {
  88. CUtlRBTree< CUtlString, int > sorted( 0, 0, ReslistLogLessFunc );
  89. for ( int i = 0; i < fileNames.Count(); ++i )
  90. {
  91. LoadResList( sorted, fileNames[ i ].String(), pchSearchPath );
  92. }
  93. // Now write it back out
  94. SaveResList( sorted, pchOutputFile, pchSearchPath );
  95. }
  96. class CWorkItem
  97. {
  98. public:
  99. CWorkItem()
  100. {
  101. }
  102. CUtlString m_sSubDir;
  103. CUtlString m_sAddCommands;
  104. };
  105. class CResListGenerator: public IResListGenerator
  106. {
  107. public:
  108. enum
  109. {
  110. STATE_BUILDINGRESLISTS = 0,
  111. STATE_GENERATINGCACHES,
  112. };
  113. CResListGenerator();
  114. virtual void Init( char const *pchBaseDir, char const *pchGameDir );
  115. virtual bool IsActive();
  116. virtual void Shutdown();
  117. virtual void Collate();
  118. virtual void SetupCommandLine();
  119. virtual bool ShouldContinue();
  120. private:
  121. bool InitCommandFile( char const *pchGameDir, char const *pchCommandFile );
  122. void LoadMapList( char const *pchGameDir, CUtlVector< CUtlString > &vecMaps, char const *pchMapFile );
  123. void CollateFiles( char const *pchResListFilename );
  124. bool m_bInitialized;
  125. bool m_bActive;
  126. CUtlString m_sBaseDir;
  127. CUtlString m_sGameDir;
  128. CUtlString m_sFullGamePath;
  129. CUtlString m_sFinalDir;
  130. CUtlString m_sWorkingDir;
  131. CUtlString m_sBaseCommandLine;
  132. CUtlString m_sOriginalCommandLine;
  133. CUtlString m_sInitialStartMap;
  134. int m_nCurrentWorkItem;
  135. CUtlVector< CWorkItem > m_WorkItems;
  136. CUtlVector< CUtlString > m_MapList;
  137. int m_nCurrentState;
  138. };
  139. static CResListGenerator g_ResListGenerator;
  140. IResListGenerator *reslistgenerator = &g_ResListGenerator;
  141. CResListGenerator::CResListGenerator() :
  142. m_bInitialized( false ),
  143. m_bActive( false ),
  144. m_nCurrentWorkItem( 0 ),
  145. m_nCurrentState( STATE_BUILDINGRESLISTS )
  146. {
  147. MEM_ALLOC_CREDIT();
  148. m_sFinalDir = "reslists";
  149. m_sWorkingDir = "reslists_work";
  150. }
  151. void CResListGenerator::CollateFiles( char const *pchResListFilename )
  152. {
  153. CUtlVector< CUtlString > vecReslists;
  154. for ( int i = 0; i < m_WorkItems.Count(); ++i )
  155. {
  156. char fn[ MAX_PATH ];
  157. Q_snprintf( fn, sizeof( fn ), "%s\\%s\\%s\\%s", m_sFullGamePath.String(), m_sWorkingDir.String(), m_WorkItems[ i ].m_sSubDir.String(), pchResListFilename );
  158. vecReslists.AddToTail( fn );
  159. }
  160. MergeResLists( vecReslists, CFmtStr( "%s\\%s\\%s", m_sFullGamePath.String(), m_sFinalDir.String(), pchResListFilename ), "GAME" );
  161. }
  162. void CResListGenerator::Init( char const *pchBaseDir, char const *pchGameDir )
  163. {
  164. if ( IsX360() )
  165. {
  166. // not used or supported
  167. return;
  168. }
  169. // Because we have to call this inside the first Apps "PreInit", we need only Init on the very first call
  170. if ( m_bInitialized )
  171. {
  172. return;
  173. }
  174. m_bInitialized = true;
  175. m_sBaseDir = pchBaseDir;
  176. m_sGameDir = pchGameDir;
  177. char path[MAX_PATH];
  178. Q_snprintf( path, sizeof(path), "%s/%s", m_sBaseDir.String(), m_sGameDir.String() );
  179. Q_FixSlashes( path );
  180. Q_strlower( path );
  181. m_sFullGamePath = path;
  182. const char *pchCommandFile = NULL;
  183. if ( CommandLine()->CheckParm( "-makereslists", &pchCommandFile ) && pchCommandFile )
  184. {
  185. // base path setup, now can get and parse command file
  186. InitCommandFile( path, pchCommandFile );
  187. }
  188. }
  189. void CResListGenerator::Shutdown()
  190. {
  191. if ( !m_bActive )
  192. return;
  193. }
  194. bool CResListGenerator::IsActive()
  195. {
  196. return m_bInitialized && m_bActive;
  197. }
  198. void CResListGenerator::Collate()
  199. {
  200. char szDir[MAX_PATH];
  201. V_snprintf( szDir, sizeof( szDir ), "%s\\%s", m_sFullGamePath.String(), m_sFinalDir.String() );
  202. g_pFullFileSystem->CreateDirHierarchy( szDir, "GAME" );
  203. // Now create the collated/merged data
  204. CollateFiles( "all.lst" );
  205. CollateFiles( "engine.lst" );
  206. for ( int i = 0 ; i < m_MapList.Count(); ++i )
  207. {
  208. CollateFiles( CFmtStr( "%s.lst", m_MapList[ i ].String() ) );
  209. }
  210. }
  211. void CResListGenerator::SetupCommandLine()
  212. {
  213. if ( !m_bActive )
  214. return;
  215. switch ( m_nCurrentState )
  216. {
  217. case STATE_BUILDINGRESLISTS:
  218. {
  219. Assert( m_nCurrentWorkItem < m_WorkItems.Count() );
  220. const CWorkItem &work = m_WorkItems[ m_nCurrentWorkItem ];
  221. // Clean the working dir
  222. char szWorkingDir[ 512 ];
  223. Q_snprintf( szWorkingDir, sizeof( szWorkingDir ), "%s\\%s", m_sWorkingDir.String(), work.m_sSubDir.String() );
  224. char szFullWorkingDir[MAX_PATH];
  225. V_snprintf( szFullWorkingDir, sizeof( szFullWorkingDir ), "%s\\%s", m_sFullGamePath.String(), szWorkingDir );
  226. g_pFullFileSystem->CreateDirHierarchy( szFullWorkingDir, "GAME" );
  227. // Preserve startmap
  228. char const *pszStartMap = NULL;
  229. CommandLine()->CheckParm( "-startmap", &pszStartMap );
  230. char szMap[ MAX_PATH ] = { 0 };
  231. if ( pszStartMap )
  232. {
  233. Q_strncpy( szMap, pszStartMap, sizeof( szMap ) );
  234. }
  235. // Prepare stuff
  236. // Reset command line based on current state
  237. char szCmd[ 512 ];
  238. Q_snprintf( szCmd, sizeof( szCmd ), "%s %s %s -reslistdir %s", m_sOriginalCommandLine.String(), m_sBaseCommandLine.String(), work.m_sAddCommands.String(), szWorkingDir );
  239. Warning( "Reslists: Setting command line:\n'%s'\n", szCmd );
  240. CommandLine()->CreateCmdLine( szCmd );
  241. // Never rebuild caches by default, inly do it in STATE_GENERATINGCACHES
  242. CommandLine()->AppendParm( "-norebuildaudio", NULL );
  243. if ( szMap[ 0 ] )
  244. {
  245. CommandLine()->AppendParm( "-startmap", szMap );
  246. }
  247. }
  248. break;
  249. case STATE_GENERATINGCACHES:
  250. {
  251. Collate();
  252. // Prepare stuff
  253. // Reset command line based on current state
  254. char szCmd[ 512 ];
  255. Q_snprintf( szCmd, sizeof( szCmd ), "%s -reslistdir %s -rebuildaudio", m_sOriginalCommandLine.String(), m_sFinalDir.String());
  256. Warning( "Caches: Setting command line:\n'%s'\n", szCmd );
  257. CommandLine()->CreateCmdLine( szCmd );
  258. CommandLine()->RemoveParm( "-norebuildaudio" );
  259. CommandLine()->RemoveParm( "-makereslists" );
  260. ++m_nCurrentState;
  261. }
  262. break;
  263. }
  264. }
  265. bool CResListGenerator::ShouldContinue()
  266. {
  267. if ( !m_bActive )
  268. return false;
  269. bool bContinueAdvancing = false;
  270. do
  271. {
  272. switch ( m_nCurrentState )
  273. {
  274. default:
  275. break;
  276. case STATE_BUILDINGRESLISTS:
  277. {
  278. CommandLine()->RemoveParm( "-startmap" );
  279. // Advance to next time
  280. ++m_nCurrentWorkItem;
  281. if ( m_nCurrentWorkItem >= m_WorkItems.Count())
  282. {
  283. // Will stay in the loop
  284. ++m_nCurrentState;
  285. bContinueAdvancing = true;
  286. }
  287. else
  288. {
  289. return true;
  290. }
  291. }
  292. break;
  293. case STATE_GENERATINGCACHES:
  294. {
  295. return true;
  296. }
  297. break;
  298. }
  299. } while ( bContinueAdvancing );
  300. return false;
  301. }
  302. void CResListGenerator::LoadMapList( char const *pchGameDir, CUtlVector< CUtlString > &vecMaps, char const *pchMapFile )
  303. {
  304. char fullpath[ 512 ];
  305. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", pchGameDir, pchMapFile );
  306. // Load them in
  307. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  308. if ( g_pFullFileSystem->ReadFile( fullpath, "GAME", buf ) )
  309. {
  310. char szMap[ MAX_PATH ];
  311. while ( true )
  312. {
  313. buf.GetLine( szMap, sizeof( szMap ) );
  314. if ( !szMap[ 0 ] )
  315. break;
  316. // Strip trailing CR/LF chars
  317. int len = Q_strlen( szMap );
  318. while ( len >= 1 && ( szMap[ len - 1 ] == '\n' || szMap[ len - 1 ] == '\r' ) )
  319. {
  320. szMap[ len - 1 ] = 0;
  321. len = Q_strlen( szMap );
  322. }
  323. CUtlString newMap;
  324. newMap = szMap;
  325. vecMaps.AddToTail( newMap );
  326. }
  327. }
  328. else
  329. {
  330. Error( "Unable to maplist file %s\n", fullpath );
  331. }
  332. }
  333. bool CResListGenerator::InitCommandFile( char const *pchGameDir, char const *pchCommandFile )
  334. {
  335. if ( *pchCommandFile == '+' ||
  336. *pchCommandFile == '-' )
  337. {
  338. Msg( "falling back to legacy reslists system\n" );
  339. return false;
  340. }
  341. char fullpath[ 512 ];
  342. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", pchGameDir, pchCommandFile );
  343. CUtlBuffer buf;
  344. if ( !g_pFullFileSystem->ReadFile( fullpath, "GAME", buf ) )
  345. {
  346. Error( "Unable to load '%s'\n", fullpath );
  347. return false;
  348. }
  349. KeyValues *kv = new KeyValues( "reslists" );
  350. if ( !kv->LoadFromBuffer( "reslists", (const char *)buf.Base() ) )
  351. {
  352. Error( "Unable to parse keyvalues from '%s'\n", fullpath );
  353. kv->deleteThis();
  354. return false;
  355. }
  356. CUtlString sMapListFile = kv->GetString( "maplist", "maplist.txt" );
  357. LoadMapList( pchGameDir, m_MapList, sMapListFile );
  358. if ( m_MapList.Count() <= 0 )
  359. {
  360. Error( "Maplist file '%s' empty or missing!!!\n", sMapListFile.String() );
  361. kv->deleteThis();
  362. return false;
  363. }
  364. char const *pszSolo = NULL;
  365. if ( CommandLine()->CheckParm( "+map", &pszSolo ) && pszSolo )
  366. {
  367. m_MapList.Purge();
  368. CUtlString newMap;
  369. newMap = pszSolo;
  370. m_MapList.AddToTail( newMap );
  371. }
  372. m_nCurrentWorkItem = CommandLine()->ParmValue( "-startstage", 0 );
  373. char const *pszStartMap = NULL;
  374. CommandLine()->CheckParm( "-startmap", &pszStartMap );
  375. if ( pszStartMap )
  376. {
  377. m_sInitialStartMap = pszStartMap;
  378. }
  379. CommandLine()->RemoveParm( "-startstage" );
  380. CommandLine()->RemoveParm( "-makereslists" );
  381. CommandLine()->RemoveParm( "-reslistdir" );
  382. CommandLine()->RemoveParm( "-norebuildaudio" );
  383. CommandLine()->RemoveParm( "-startmap" );
  384. m_sOriginalCommandLine = CommandLine()->GetCmdLine();
  385. // Add it back in for first map
  386. if ( pszStartMap )
  387. {
  388. CommandLine()->AppendParm( "-startmap", m_sInitialStartMap.String() );
  389. }
  390. m_sBaseCommandLine = kv->GetString( "basecommandline", "" );
  391. m_sFinalDir = kv->GetString( "finaldir", m_sFinalDir.String() );
  392. m_sWorkingDir = kv->GetString( "workdir", m_sWorkingDir.String() );
  393. int i = 0;
  394. do
  395. {
  396. char sz[ 32 ];
  397. Q_snprintf( sz, sizeof( sz ), "%i", i );
  398. KeyValues *subKey = kv->FindKey( sz, false );
  399. if ( !subKey )
  400. break;
  401. CWorkItem work;
  402. work.m_sSubDir = subKey->GetString( "subdir", "" );
  403. work.m_sAddCommands = subKey->GetString( "addcommands", "" );
  404. if ( work.m_sSubDir.Length() > 0 )
  405. {
  406. m_WorkItems.AddToTail( work );
  407. }
  408. else
  409. {
  410. Error( "%s: failed to specify 'subdir' for item %s\n", fullpath, sz );
  411. }
  412. ++i;
  413. } while ( true );
  414. m_bActive = m_WorkItems.Count() > 0;
  415. m_nCurrentWorkItem = clamp( m_nCurrentWorkItem, 0, m_WorkItems.Count() - 1 );
  416. bool bCollate = CommandLine()->CheckParm( "-collate" ) ? true : false;
  417. if ( bCollate )
  418. {
  419. Collate();
  420. m_bActive = false;
  421. exit( -1 );
  422. }
  423. kv->deleteThis();
  424. /*
  425. if ( m_bActive )
  426. {
  427. // Wipe console log
  428. g_pFullFileSystem->RemoveFile( "console.log", "GAME" );
  429. }
  430. */
  431. return m_bActive;
  432. }