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.

555 lines
14 KiB

  1. //===== Copyright � 1996-2005, 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_SETUP = 0,
  111. STATE_BUILDINGRESLISTS,
  112. STATE_GENERATINGCACHES,
  113. STATE_MAXSTATES
  114. };
  115. CResListGenerator();
  116. virtual void Init( char const *pchBaseDir, char const *pchGameDir );
  117. virtual bool IsActive();
  118. virtual void Shutdown();
  119. virtual bool TickAndFixupCommandLine();
  120. virtual bool ShouldContinue();
  121. private:
  122. bool InitCommandFile( char const *pchGameDir, char const *pchCommandFile );
  123. void LoadMapList( char const *pchGameDir, CUtlVector< CUtlString > &vecMaps, char const *pchMapFile );
  124. void CollateFiles( char const *pchResListFilename );
  125. void Collate();
  126. bool m_bInitialized;
  127. bool m_bActive;
  128. bool m_bCreatingForXbox;
  129. CUtlString m_sBaseDir;
  130. CUtlString m_sGameDir;
  131. CUtlString m_sFullGamePath;
  132. CUtlString m_sFinalDir;
  133. CUtlString m_sWorkingDir;
  134. CUtlString m_sBaseCommandLine;
  135. CUtlString m_sOriginalCommandLine;
  136. CUtlString m_sInitialStartMap;
  137. int m_nCurrentWorkItem;
  138. CUtlVector< CWorkItem > m_WorkItems;
  139. CUtlVector< CUtlString > m_MapList;
  140. int m_nCurrentState;
  141. };
  142. static CResListGenerator g_ResListGenerator;
  143. IResListGenerator *reslistgenerator = &g_ResListGenerator;
  144. CResListGenerator::CResListGenerator() :
  145. m_bInitialized( false ),
  146. m_bActive( false ),
  147. m_bCreatingForXbox( false ),
  148. m_nCurrentWorkItem( 0 ),
  149. m_nCurrentState( STATE_SETUP )
  150. {
  151. MEM_ALLOC_CREDIT();
  152. m_sFinalDir = "reslists";
  153. m_sWorkingDir = "reslists_work";
  154. }
  155. void CResListGenerator::CollateFiles( char const *pchResListFilename )
  156. {
  157. CUtlVector< CUtlString > vecReslists;
  158. for ( int i = 0; i < m_WorkItems.Count(); ++i )
  159. {
  160. char fn[ MAX_PATH ];
  161. Q_snprintf( fn, sizeof( fn ), "%s\\%s\\%s\\%s", m_sFullGamePath.String(), m_sWorkingDir.String(), m_WorkItems[ i ].m_sSubDir.String(), pchResListFilename );
  162. vecReslists.AddToTail( fn );
  163. }
  164. MergeResLists( vecReslists, CFmtStr( "%s\\%s\\%s", m_sFullGamePath.String(), m_sFinalDir.String(), pchResListFilename ), "GAME" );
  165. }
  166. void CResListGenerator::Init( char const *pchBaseDir, char const *pchGameDir )
  167. {
  168. if ( IsX360() )
  169. {
  170. // not used or supported, PC builds them for Xbox
  171. return;
  172. }
  173. // Because we have to call this inside the first Apps "PreInit", we need only Init on the very first call
  174. if ( m_bInitialized )
  175. {
  176. return;
  177. }
  178. m_bInitialized = true;
  179. m_sBaseDir = pchBaseDir;
  180. m_sGameDir = pchGameDir;
  181. char path[MAX_PATH];
  182. Q_snprintf( path, sizeof(path), "%s/%s", m_sBaseDir.String(), m_sGameDir.String() );
  183. Q_FixSlashes( path );
  184. Q_strlower( path );
  185. m_sFullGamePath = path;
  186. const char *pchCommandFile = NULL;
  187. if ( CommandLine()->CheckParm( "-makereslists", &pchCommandFile ) && pchCommandFile )
  188. {
  189. // base path setup, now can get and parse command file
  190. // one time setup ONLY
  191. InitCommandFile( path, pchCommandFile );
  192. }
  193. }
  194. void CResListGenerator::Shutdown()
  195. {
  196. if ( !m_bActive )
  197. return;
  198. }
  199. bool CResListGenerator::IsActive()
  200. {
  201. return m_bInitialized && m_bActive;
  202. }
  203. void CResListGenerator::Collate()
  204. {
  205. char szDir[MAX_PATH];
  206. V_snprintf( szDir, sizeof( szDir ), "%s\\%s", m_sFullGamePath.String(), m_sFinalDir.String() );
  207. g_pFullFileSystem->CreateDirHierarchy( szDir, "GAME" );
  208. // Now create the collated/merged data
  209. CollateFiles( "all.lst" );
  210. CollateFiles( "engine.lst" );
  211. for ( int i = 0 ; i < m_MapList.Count(); ++i )
  212. {
  213. CollateFiles( CFmtStr( "%s.lst", m_MapList[ i ].String() ) );
  214. }
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Called at each restart invocation, clocks the state.
  218. // Returns TRUE if caller should proceed with command line, FALSE otherwise.
  219. // FALSE is used to stop the reslist process which requires additional post passes.
  220. //-----------------------------------------------------------------------------
  221. bool CResListGenerator::TickAndFixupCommandLine()
  222. {
  223. if ( !m_bActive )
  224. {
  225. return true;
  226. }
  227. // clock the state
  228. switch ( m_nCurrentState )
  229. {
  230. default:
  231. m_bActive = false;
  232. break;
  233. case STATE_SETUP:
  234. // first time
  235. m_nCurrentState = STATE_BUILDINGRESLISTS;
  236. break;
  237. case STATE_BUILDINGRESLISTS:
  238. {
  239. CommandLine()->RemoveParm( "-startmap" );
  240. // Advance to next item
  241. ++m_nCurrentWorkItem;
  242. if ( m_nCurrentWorkItem >= m_WorkItems.Count() )
  243. {
  244. // out of work, finalize
  245. Collate();
  246. // advance to next state
  247. ++m_nCurrentState;
  248. }
  249. }
  250. break;
  251. case STATE_GENERATINGCACHES:
  252. ++m_nCurrentState;
  253. break;
  254. }
  255. switch ( m_nCurrentState )
  256. {
  257. default:
  258. m_bActive = false;
  259. break;
  260. case STATE_BUILDINGRESLISTS:
  261. {
  262. Assert( m_nCurrentWorkItem < m_WorkItems.Count() );
  263. const CWorkItem &work = m_WorkItems[ m_nCurrentWorkItem ];
  264. // Clean the working dir
  265. char szWorkingDir[ 512 ];
  266. Q_snprintf( szWorkingDir, sizeof( szWorkingDir ), "%s\\%s", m_sWorkingDir.String(), work.m_sSubDir.String() );
  267. char szFullWorkingDir[MAX_PATH];
  268. V_snprintf( szFullWorkingDir, sizeof( szFullWorkingDir ), "%s\\%s", m_sFullGamePath.String(), szWorkingDir );
  269. g_pFullFileSystem->CreateDirHierarchy( szFullWorkingDir, "GAME" );
  270. // Preserve startmap
  271. char const *pszStartMap = NULL;
  272. CommandLine()->CheckParm( "-startmap", &pszStartMap );
  273. char szMap[ MAX_PATH ] = { 0 };
  274. if ( pszStartMap )
  275. {
  276. Q_strncpy( szMap, pszStartMap, sizeof( szMap ) );
  277. }
  278. // Prepare stuff
  279. // Reset command line based on current state
  280. char szCmd[ 512 ];
  281. Q_snprintf( szCmd, sizeof( szCmd ), "%s %s %s -reslistdir %s", m_sOriginalCommandLine.String(), m_sBaseCommandLine.String(), work.m_sAddCommands.String(), szWorkingDir );
  282. CommandLine()->CreateCmdLine( szCmd );
  283. // Never rebuild caches by default, inly do it in STATE_GENERATINGCACHES
  284. CommandLine()->AppendParm( "-norebuildaudio", NULL );
  285. if ( szMap[ 0 ] )
  286. {
  287. CommandLine()->AppendParm( "-startmap", szMap );
  288. }
  289. if ( m_bCreatingForXbox )
  290. {
  291. CommandLine()->AppendParm( "-xboxreslist", NULL );
  292. }
  293. Warning( "Generating Reslists: Setting command line:\n'%s'\n", CommandLine()->GetCmdLine() );
  294. }
  295. break;
  296. case STATE_GENERATINGCACHES:
  297. {
  298. if ( m_bCreatingForXbox )
  299. {
  300. // Xbox has no caches, process finished
  301. m_bActive = false;
  302. break;
  303. }
  304. // Prepare stuff
  305. // Reset command line based on current state
  306. char szCmd[ 512 ];
  307. Q_snprintf( szCmd, sizeof( szCmd ), "%s -reslistdir %s -rebuildaudio", m_sOriginalCommandLine.String(), m_sFinalDir.String());
  308. CommandLine()->CreateCmdLine( szCmd );
  309. CommandLine()->RemoveParm( "-norebuildaudio" );
  310. CommandLine()->RemoveParm( "-makereslists" );
  311. Warning( "Generating Caches: Setting command line:\n'%s'\n", CommandLine()->GetCmdLine() );
  312. }
  313. break;
  314. }
  315. if ( !m_bActive )
  316. {
  317. // no further processing required, make the engine shut down cleanly in this pass
  318. CommandLine()->RemoveParm( "-makereslists" );
  319. CommandLine()->AppendParm( "-autoquit", NULL );
  320. }
  321. // continue
  322. return m_bActive;
  323. }
  324. bool CResListGenerator::ShouldContinue()
  325. {
  326. // some states require post processing
  327. // let the state processing determine the final quit state
  328. if ( !m_bActive || m_nCurrentState >= STATE_MAXSTATES )
  329. {
  330. return false;
  331. }
  332. return true;
  333. }
  334. void CResListGenerator::LoadMapList( char const *pchGameDir, CUtlVector< CUtlString > &vecMaps, char const *pchMapFile )
  335. {
  336. char fullpath[ 512 ];
  337. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", pchGameDir, pchMapFile );
  338. // Load them in
  339. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  340. if ( g_pFullFileSystem->ReadFile( fullpath, "GAME", buf ) )
  341. {
  342. char szMap[ MAX_PATH ];
  343. while ( true )
  344. {
  345. buf.GetLine( szMap, sizeof( szMap ) );
  346. if ( !szMap[ 0 ] )
  347. break;
  348. // Strip trailing CR/LF chars
  349. int len = Q_strlen( szMap );
  350. while ( len >= 1 && ( ( szMap[ len - 1 ] == '\n' ) || ( szMap[ len - 1 ] == '\r' ) ) )
  351. {
  352. szMap[ len - 1 ] = 0;
  353. len = Q_strlen( szMap );
  354. }
  355. CUtlString newMap;
  356. newMap = szMap;
  357. vecMaps.AddToTail( newMap );
  358. }
  359. }
  360. else
  361. {
  362. Error( "Unable to maplist file %s\n", fullpath );
  363. }
  364. }
  365. bool CResListGenerator::InitCommandFile( char const *pchGameDir, char const *pchCommandFile )
  366. {
  367. if ( *pchCommandFile == '+' ||
  368. *pchCommandFile == '-' )
  369. {
  370. Msg( "CResListGenerator: Falling back to legacy reslists system.\n" );
  371. return false;
  372. }
  373. char fullpath[ 512 ];
  374. Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", pchGameDir, pchCommandFile );
  375. CUtlBuffer buf;
  376. if ( !g_pFullFileSystem->ReadFile( fullpath, "GAME", buf ) )
  377. {
  378. Error( "Unable to load '%s'\n", fullpath );
  379. return false;
  380. }
  381. KeyValues *kv = new KeyValues( "reslists" );
  382. if ( !kv->LoadFromBuffer( "reslists", (const char *)buf.Base() ) )
  383. {
  384. Error( "Unable to parse keyvalues from '%s'\n", fullpath );
  385. kv->deleteThis();
  386. return false;
  387. }
  388. CUtlString sMapListFile = kv->GetString( "maplist", "maplist.txt" );
  389. LoadMapList( pchGameDir, m_MapList, sMapListFile );
  390. if ( m_MapList.Count() <= 0 )
  391. {
  392. Error( "Maplist file '%s' empty or missing!!!\n", sMapListFile.String() );
  393. kv->deleteThis();
  394. return false;
  395. }
  396. char const *pszSolo = NULL;
  397. if ( CommandLine()->CheckParm( "-reslistmap", &pszSolo ) && pszSolo )
  398. {
  399. m_MapList.Purge();
  400. CUtlString newMap;
  401. newMap = pszSolo;
  402. m_MapList.AddToTail( newMap );
  403. }
  404. m_nCurrentWorkItem = CommandLine()->ParmValue( "-startstage", 0 );
  405. char const *pszStartMap = NULL;
  406. CommandLine()->CheckParm( "-startmap", &pszStartMap );
  407. if ( pszStartMap )
  408. {
  409. m_sInitialStartMap = pszStartMap;
  410. }
  411. CommandLine()->RemoveParm( "-startstage" );
  412. CommandLine()->RemoveParm( "-makereslists" );
  413. CommandLine()->RemoveParm( "-reslistdir" );
  414. CommandLine()->RemoveParm( "-norebuildaudio" );
  415. CommandLine()->RemoveParm( "-startmap" );
  416. m_sOriginalCommandLine = CommandLine()->GetCmdLine();
  417. // Add it back in for first map
  418. if ( pszStartMap )
  419. {
  420. CommandLine()->AppendParm( "-startmap", m_sInitialStartMap.String() );
  421. }
  422. m_sBaseCommandLine = kv->GetString( "basecommandline", "" );
  423. m_sFinalDir = kv->GetString( "finaldir", m_sFinalDir.String() );
  424. m_sWorkingDir = kv->GetString( "workdir", m_sWorkingDir.String() );
  425. m_bCreatingForXbox = kv->GetInt( "xbox", 0 ) != 0;
  426. int i = 0;
  427. do
  428. {
  429. char sz[ 32 ];
  430. Q_snprintf( sz, sizeof( sz ), "%i", i );
  431. KeyValues *subKey = kv->FindKey( sz, false );
  432. if ( !subKey )
  433. break;
  434. CWorkItem work;
  435. work.m_sSubDir = subKey->GetString( "subdir", "" );
  436. work.m_sAddCommands = subKey->GetString( "addcommands", "" );
  437. if ( work.m_sSubDir.Length() > 0 )
  438. {
  439. m_WorkItems.AddToTail( work );
  440. }
  441. else
  442. {
  443. Error( "%s: failed to specify 'subdir' for item %s\n", fullpath, sz );
  444. }
  445. ++i;
  446. } while ( true );
  447. m_bActive = m_WorkItems.Count() > 0;
  448. m_nCurrentWorkItem = clamp( m_nCurrentWorkItem, 0, m_WorkItems.Count() - 1 );
  449. bool bCollate = CommandLine()->CheckParm( "-collate" ) ? true : false;
  450. if ( bCollate )
  451. {
  452. Collate();
  453. m_bActive = false;
  454. exit( -1 );
  455. }
  456. kv->deleteThis();
  457. return m_bActive;
  458. }