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.

641 lines
17 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include <tier0/dbg.h>
  7. #include <tier1/strtools.h>
  8. #include <utlbuffer.h>
  9. #include "demofile.h"
  10. #include "filesystem_engine.h"
  11. #include "demo.h"
  12. #include "demobuffer.h"
  13. #include "convar.h" // For dbg_demofile
  14. #include "host_cmd.h"
  15. #include "cdll_int.h" // For playback parameters
  16. // NOTE: This has to be the last file included!
  17. #include "tier0/memdbgon.h"
  18. void Host_EndGame (bool bShowMainMenu, const char *message, ...);
  19. // Temporary debug stuff:
  20. #define DEMOFILE_DBG_PRINT
  21. ConVar dbg_demofile( "dbg_demofile", "0" );
  22. #if defined( DEMOFILE_DBG_PRINT )
  23. class CDbgPrint
  24. {
  25. public:
  26. static int s_nIndent;
  27. CDbgPrint( const char *pMsg )
  28. {
  29. ++s_nIndent;
  30. if ( dbg_demofile.GetInt() )
  31. {
  32. for (int i = 0; i < 3*s_nIndent; ++i)
  33. DevMsg(" ");
  34. DevMsg( "%s", pMsg );
  35. }
  36. }
  37. ~CDbgPrint() { --s_nIndent; }
  38. };
  39. int CDbgPrint::s_nIndent = 0;
  40. #define DemoFileDbg(_txt) CDbgPrint printer( _txt )
  41. #else
  42. #define DemoFileDbg(_txt) (void)0
  43. #endif
  44. ConVar demo_strict_validation( "demo_strict_validation", "0", FCVAR_RELEASE );
  45. //////////////////////////////////////////////////////////////////////
  46. // Construction/Destruction
  47. //////////////////////////////////////////////////////////////////////
  48. CDemoFile::CDemoFile() :
  49. m_pBuffer( NULL )
  50. {
  51. }
  52. CDemoFile::~CDemoFile()
  53. {
  54. if ( IsOpen() )
  55. {
  56. Close();
  57. }
  58. }
  59. void CDemoFile::WriteSequenceInfo(int nSeqNrIn, int nSeqNrOut)
  60. {
  61. DemoFileDbg( "WriteSequenceInfo()\n" );
  62. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  63. m_pBuffer->PutInt( nSeqNrIn );
  64. m_pBuffer->PutInt( nSeqNrOut );
  65. }
  66. //-----------------------------------------------------------------------------
  67. // Purpose:
  68. //-----------------------------------------------------------------------------
  69. void CDemoFile::ReadSequenceInfo(int &nSeqNrIn, int &nSeqNrOut)
  70. {
  71. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  72. nSeqNrIn = m_pBuffer->GetInt( );
  73. nSeqNrOut = m_pBuffer->GetInt( );
  74. }
  75. inline void ByteSwap_democmdinfo_t( democmdinfo_t &cmdInfo )
  76. {
  77. for ( int i = 0; i < MAX_SPLITSCREEN_CLIENTS; ++i )
  78. {
  79. democmdinfo_t::Split_t &swap = cmdInfo.u[ i ];
  80. swap.flags = LittleDWord( swap.flags );
  81. LittleFloat( &swap.viewOrigin.x, &swap.viewOrigin.x );
  82. LittleFloat( &swap.viewOrigin.y, &swap.viewOrigin.y );
  83. LittleFloat( &swap.viewOrigin.z, &swap.viewOrigin.z );
  84. LittleFloat( &swap.viewAngles.x, &swap.viewAngles.x );
  85. LittleFloat( &swap.viewAngles.y, &swap.viewAngles.y );
  86. LittleFloat( &swap.viewAngles.z, &swap.viewAngles.z );
  87. LittleFloat( &swap.localViewAngles.x, &swap.localViewAngles.x );
  88. LittleFloat( &swap.localViewAngles.y, &swap.localViewAngles.y );
  89. LittleFloat( &swap.localViewAngles.z, &swap.localViewAngles.z );
  90. LittleFloat( &swap.viewOrigin2.x, &swap.viewOrigin2.x );
  91. LittleFloat( &swap.viewOrigin2.y, &swap.viewOrigin2.y );
  92. LittleFloat( &swap.viewOrigin2.z, &swap.viewOrigin2.z );
  93. LittleFloat( &swap.viewAngles2.x, &swap.viewAngles2.x );
  94. LittleFloat( &swap.viewAngles2.y, &swap.viewAngles2.y );
  95. LittleFloat( &swap.viewAngles2.z, &swap.viewAngles2.z );
  96. LittleFloat( &swap.localViewAngles2.x, &swap.localViewAngles2.x );
  97. LittleFloat( &swap.localViewAngles2.y, &swap.localViewAngles2.y );
  98. LittleFloat( &swap.localViewAngles2.z, &swap.localViewAngles2.z );
  99. }
  100. }
  101. void CDemoFile::WriteCmdInfo( democmdinfo_t& info )
  102. {
  103. DemoFileDbg( "WriteCmdInfo()\n" );
  104. democmdinfo_t littleEndianInfo = info;
  105. ByteSwap_democmdinfo_t( littleEndianInfo );
  106. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  107. m_pBuffer->Put( &littleEndianInfo, sizeof(democmdinfo_t) );
  108. }
  109. //-----------------------------------------------------------------------------
  110. // Purpose:
  111. //-----------------------------------------------------------------------------
  112. void CDemoFile::ReadCmdInfo( democmdinfo_t& info )
  113. {
  114. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  115. m_pBuffer->Get( &info, sizeof(democmdinfo_t) );
  116. ByteSwap_democmdinfo_t( info );
  117. }
  118. //-----------------------------------------------------------------------------
  119. // Purpose:
  120. // Input : cmd -
  121. // *fp -
  122. //-----------------------------------------------------------------------------
  123. void CDemoFile::WriteCmdHeader( unsigned char cmd, int tick, int nPlayerSlot )
  124. {
  125. if ( dbg_demofile.GetInt() ) DevMsg( "----------------------------------------\n" );
  126. Assert( cmd >= dem_signon && cmd <= dem_lastcmd );
  127. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  128. m_pBuffer->PutUnsignedChar( cmd );
  129. m_pBuffer->WriteTick( tick );
  130. Assert( nPlayerSlot >= 0 &&
  131. nPlayerSlot < MAX_SPLITSCREEN_CLIENTS );
  132. m_pBuffer->PutChar( nPlayerSlot );
  133. char *cmdname[] =
  134. {
  135. "dem_unknown",
  136. "dem_signon",
  137. "dem_packet",
  138. "dem_synctick",
  139. "dem_consolecmd",
  140. "dem_usercmd",
  141. "dem_datatables",
  142. "dem_stop",
  143. "dem_customdata",
  144. "dem_stringtables"
  145. };
  146. DemoFileDbg( "WriteCmdHeader()..." );
  147. if ( dbg_demofile.GetInt() ) DevMsg( "tick %i, cmd %s \n", tick, cmdname[cmd] );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose:
  151. // Input : cmd -
  152. // dt -
  153. // frame -
  154. //-----------------------------------------------------------------------------
  155. void CDemoFile::ReadCmdHeader( unsigned char& cmd, int& tick, int &nPlayerSlot )
  156. {
  157. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  158. cmd = m_pBuffer->GetUnsignedChar( );
  159. if ( !m_pBuffer || !m_pBuffer->IsValid() )
  160. {
  161. ConDMsg("Missing end tag in demo file.\n");
  162. cmd = dem_stop;
  163. return;
  164. }
  165. if ( cmd <= 0 || cmd > dem_lastcmd )
  166. {
  167. ConDMsg("Unexepcted command token [%d] in .demo file\n", cmd );
  168. cmd = dem_stop;
  169. return;
  170. }
  171. tick = m_pBuffer->GetInt( );
  172. nPlayerSlot = (int)m_pBuffer->GetChar();
  173. }
  174. void CDemoFile::WriteConsoleCommand( const char *cmdstring, int tick, int nPlayerSlot )
  175. {
  176. DemoFileDbg( "WriteConsoleCommand()\n" );
  177. if ( !cmdstring || !cmdstring[0] )
  178. return;
  179. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  180. return;
  181. int len = Q_strlen( cmdstring ) + 1;
  182. if ( len >= 1024 )
  183. {
  184. DevMsg("CDemoFile::WriteConsoleCommand: command too long (>1024).\n");
  185. return;
  186. }
  187. WriteCmdHeader( dem_consolecmd, tick, nPlayerSlot );
  188. WriteRawData( cmdstring, len );
  189. }
  190. const char *CDemoFile::ReadConsoleCommand()
  191. {
  192. static char cmdstring[1024];
  193. ReadRawData( cmdstring, sizeof(cmdstring) );
  194. return cmdstring;
  195. }
  196. unsigned int CDemoFile::GetCurPos( bool bRead )
  197. {
  198. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  199. return 0;
  200. if ( bRead )
  201. return m_pBuffer->TellGet();
  202. return m_pBuffer->TellPut();
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Purpose:
  206. // Input : buf -
  207. //-----------------------------------------------------------------------------
  208. void CDemoFile::WriteNetworkDataTables( bf_write *buf, int tick )
  209. {
  210. DemoFileDbg( "WriteNetworkDataTables()\n" );
  211. MEM_ALLOC_CREDIT();
  212. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  213. {
  214. DevMsg("CDemoFile::WriteNetworkDataTables: Haven't opened file yet!\n" );
  215. return;
  216. }
  217. WriteCmdHeader( dem_datatables, tick, 0 );
  218. WriteRawData( (char*)buf->GetBasePointer(), buf->GetNumBytesWritten() );
  219. }
  220. //-----------------------------------------------------------------------------
  221. // Purpose:
  222. // Input : expected_length -
  223. // &demofile -
  224. //-----------------------------------------------------------------------------
  225. int CDemoFile::ReadNetworkDataTables( bf_read *buf )
  226. {
  227. if ( buf )
  228. return ReadRawData( (char*)buf->GetBasePointer(), buf->GetNumBytesLeft() );
  229. return ReadRawData( NULL, 0 ); // skip data
  230. }
  231. void CDemoFile::WriteStringTables( bf_write *buf, int tick )
  232. {
  233. DemoFileDbg( "WriteStringTables()\n" );
  234. MEM_ALLOC_CREDIT();
  235. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  236. {
  237. DevMsg("CDemoFile::WriteStringTables: Haven't opened file yet!\n" );
  238. return;
  239. }
  240. WriteCmdHeader( dem_stringtables, tick, 0 );
  241. WriteRawData( (char*)buf->GetBasePointer(), buf->GetNumBytesWritten() );
  242. }
  243. int CDemoFile::ReadStringTables( bf_read *buf )
  244. {
  245. if ( buf )
  246. return ReadRawData( (char*)buf->GetBasePointer(), buf->GetNumBytesLeft() );
  247. return ReadRawData( NULL, 0 ); // skip data
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Purpose:
  251. // Input : cmdnumber -
  252. //-----------------------------------------------------------------------------
  253. void CDemoFile::WriteUserCmd( int cmdnumber, const char *buffer, unsigned char bytes, int tick, int nPlayerSlot )
  254. {
  255. DemoFileDbg( "WriteUserCmd()\n" );
  256. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  257. return;
  258. WriteCmdHeader( dem_usercmd, tick, nPlayerSlot );
  259. m_pBuffer->PutInt( cmdnumber );
  260. WriteRawData( buffer, bytes );
  261. }
  262. //-----------------------------------------------------------------------------
  263. // Purpose:
  264. // Input : discard -
  265. //-----------------------------------------------------------------------------
  266. int CDemoFile::ReadUserCmd( char *buffer, int &size )
  267. {
  268. int outgoing_sequence;
  269. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  270. outgoing_sequence = m_pBuffer->GetInt();
  271. size = ReadRawData( buffer, size );
  272. return outgoing_sequence;
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose:
  276. //-----------------------------------------------------------------------------
  277. void CDemoFile::WriteCustomData( int iCallbackIndex, const void *pData, size_t iDataSize, int tick )
  278. {
  279. DemoFileDbg( "WriteCustomData()\n" );
  280. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  281. return;
  282. WriteCmdHeader( dem_customdata, tick, 0 );
  283. //write the index of the callback according to the table we saved earlier
  284. m_pBuffer->PutInt( iCallbackIndex );
  285. MEM_ALLOC_CREDIT();
  286. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  287. m_pBuffer->PutInt( iDataSize );
  288. m_pBuffer->Put( pData, iDataSize );
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Purpose: Grabs the callback id and data for a custom data chunk
  292. //-----------------------------------------------------------------------------
  293. int CDemoFile::ReadCustomData( int *pCallbackIndex, uint8 **ppDataChunk )
  294. {
  295. static CUtlVector<uint8> s_TempMemoryBuffer;
  296. int size;
  297. int iCallbackIndex;
  298. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  299. iCallbackIndex = m_pBuffer->GetInt();
  300. size = m_pBuffer->GetInt();
  301. if( (pCallbackIndex == NULL) || (ppDataChunk == NULL) )
  302. {
  303. //skip the chunk
  304. m_pBuffer->SeekGet( false, size );
  305. return 0;
  306. }
  307. s_TempMemoryBuffer.SetSize( size );
  308. *ppDataChunk = s_TempMemoryBuffer.Base();
  309. *pCallbackIndex = iCallbackIndex;
  310. // read data into buffer
  311. m_pBuffer->Get( s_TempMemoryBuffer.Base(), size );
  312. if ( !m_pBuffer || !m_pBuffer->IsValid() )
  313. {
  314. if ( demo_strict_validation.GetInt() )
  315. Host_EndGame(true, "Error reading demo message data.\n");
  316. return -1;
  317. }
  318. return size;
  319. }
  320. //-----------------------------------------------------------------------------
  321. // Purpose: Rewind from the current spot by the time stamp, byte code and frame counter offsets
  322. //-----------------------------------------------------------------------------
  323. void CDemoFile::SeekTo( int position, bool bRead )
  324. {
  325. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  326. if ( bRead )
  327. {
  328. m_pBuffer->SeekGet( true, position );
  329. }
  330. else
  331. {
  332. m_pBuffer->SeekPut( true, position );
  333. }
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Purpose:
  337. // Output : Returns true on success, false on failure.
  338. //-----------------------------------------------------------------------------
  339. int CDemoFile::ReadRawData( char *buffer, int length )
  340. {
  341. int size;
  342. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  343. size = m_pBuffer->GetInt();
  344. if ( buffer && (length < size) )
  345. {
  346. DevMsg("CDemoFile::ReadRawData: buffer overflow (%i).\n", size );
  347. return -1;
  348. }
  349. if ( !buffer )
  350. {
  351. // just skip it
  352. m_pBuffer->SeekGet( false, size );
  353. return size;
  354. }
  355. if ( length < size )
  356. {
  357. // given buffer is too small
  358. DevMsg("CDemoFile::ReadRawData: buffer overflow (%i).\n", size );
  359. m_pBuffer->SeekGet( false, size );
  360. return -1;
  361. }
  362. // read data into buffer
  363. m_pBuffer->Get( buffer, size );
  364. if ( !m_pBuffer || !m_pBuffer->IsValid() )
  365. {
  366. if ( demo_strict_validation.GetInt() )
  367. Host_EndGame( true, "Error reading demo message data.\n" );
  368. return -1;
  369. }
  370. return size;
  371. }
  372. void CDemoFile::WriteRawData( const char *buffer, int length )
  373. {
  374. DemoFileDbg( "WriteRawData()\n" );
  375. MEM_ALLOC_CREDIT();
  376. Assert( m_pBuffer && m_pBuffer->IsInitialized() );
  377. m_pBuffer->PutInt( length );
  378. m_pBuffer->Put( buffer, length );
  379. }
  380. void CDemoFile::WriteDemoHeader()
  381. {
  382. DemoFileDbg( "WriteDemoHeader()\n" );
  383. Assert( m_DemoHeader.networkprotocol == GetHostVersion() );
  384. DevMsg( "\n" );
  385. DevMsg( " demofilestamp: %s\n", m_DemoHeader.demofilestamp );
  386. DevMsg( " demoprotocol (should be %i): %i\n", DEMO_PROTOCOL, m_DemoHeader.demoprotocol );
  387. DevMsg( " networkprotocol (should be %i): %i\n", GetHostVersion(), m_DemoHeader.networkprotocol );
  388. DevMsg( " servername: %s\n", m_DemoHeader.servername );
  389. DevMsg( " clientname: %s\n", m_DemoHeader.clientname );
  390. DevMsg( " mapname: %s\n", m_DemoHeader.mapname );
  391. DevMsg( " gamedirectory: %s\n", m_DemoHeader.gamedirectory );
  392. DevMsg( " playback_time: %f\n", m_DemoHeader.playback_time );
  393. DevMsg( " playback_ticks: %i\n", m_DemoHeader.playback_ticks );
  394. DevMsg( " playback_frames: %i\n", m_DemoHeader.playback_frames );
  395. DevMsg( " signonlength: %i\n", m_DemoHeader.signonlength );
  396. DevMsg( "\n" );
  397. // Swaps endianness, goes to file start and writes header
  398. m_pBuffer->WriteHeader( &m_DemoHeader, sizeof(demoheader_t) );
  399. }
  400. demoheader_t *CDemoFile::ReadDemoHeader( CDemoPlaybackParameters_t const *pPlaybackParameters )
  401. {
  402. bool bOk;
  403. Q_memset( &m_DemoHeader, 0, sizeof(m_DemoHeader) );
  404. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  405. return NULL;
  406. m_pBuffer->SeekGet( true, pPlaybackParameters ? pPlaybackParameters->m_uiHeaderPrefixLength : 0 );
  407. m_pBuffer->Get( &m_DemoHeader, sizeof(demoheader_t) );
  408. bOk = m_pBuffer->IsValid();
  409. ByteSwap_demoheader_t( m_DemoHeader );
  410. if ( !bOk )
  411. return NULL; // reading failed
  412. if ( Q_strcmp( m_DemoHeader.demofilestamp, DEMO_HEADER_ID ) )
  413. {
  414. ConMsg( "%s has invalid demo header ID.\n", m_szFileName );
  415. return NULL;
  416. }
  417. if ( ( m_DemoHeader.networkprotocol != GetHostVersion() ) &&
  418. ( !pPlaybackParameters || !pPlaybackParameters->m_bAnonymousPlayerIdentity ) )
  419. {
  420. ConMsg ("WARNING: demo network protocol %i differs, engine version is %i \n",
  421. m_DemoHeader.networkprotocol, GetHostVersion() );
  422. }
  423. if ( ( m_DemoHeader.demoprotocol > DEMO_PROTOCOL) ||
  424. ( m_DemoHeader.demoprotocol < 2 ) )
  425. {
  426. ConMsg ("ERROR: demo file protocol %i outdated, engine version is %i \n",
  427. m_DemoHeader.demoprotocol, DEMO_PROTOCOL );
  428. return NULL;
  429. }
  430. return &m_DemoHeader;
  431. }
  432. void CDemoFile::WriteFileBytes( FileHandle_t fh, int length )
  433. {
  434. DemoFileDbg( "WriteFileBytes()\n" );
  435. int copysize = length;
  436. char copybuf[COM_COPY_CHUNK_SIZE];
  437. while ( copysize > COM_COPY_CHUNK_SIZE )
  438. {
  439. g_pFileSystem->Read ( copybuf, COM_COPY_CHUNK_SIZE, fh );
  440. m_pBuffer->Put( copybuf, COM_COPY_CHUNK_SIZE );
  441. copysize -= COM_COPY_CHUNK_SIZE;
  442. }
  443. g_pFileSystem->Read ( copybuf, copysize, fh );
  444. m_pBuffer->Put( copybuf, copysize );
  445. g_pFileSystem->Flush ( fh );
  446. }
  447. float CDemoFile::GetTicksPerSecond()
  448. {
  449. return m_DemoHeader.playback_ticks / m_DemoHeader.playback_time;
  450. }
  451. float CDemoFile::GetTicksPerFrame()
  452. {
  453. return m_DemoHeader.playback_ticks / m_DemoHeader.playback_frames;
  454. }
  455. int CDemoFile::GetTotalTicks( void )
  456. {
  457. return m_DemoHeader.playback_ticks;
  458. }
  459. bool CDemoFile::Open( const char *name, bool bReadOnly, bool bMemoryBuffer )
  460. {
  461. if ( m_pBuffer && m_pBuffer->IsInitialized() )
  462. {
  463. ConMsg( "CDemoFile::Open: file already open.\n" );
  464. return false;
  465. }
  466. m_szFileName[0] = 0; // clear name
  467. Q_memset( &m_DemoHeader, 0, sizeof(m_DemoHeader) ); // and demo header
  468. if ( bMemoryBuffer )
  469. {
  470. Assert( !bReadOnly ); // Only read from files
  471. int const nMaxSize = 1024 * 1024 * 16;
  472. MemoryDemoBufferInitParams_t params( nMaxSize );
  473. m_pBuffer = CreateDemoBuffer( true, params );
  474. }
  475. else
  476. {
  477. StreamDemoBufferInitParams_t params( name, NULL, bReadOnly ? CUtlBuffer::READ_ONLY : 0, IsX360() ? FSOPEN_NEVERINPACK : 0 );
  478. m_pBuffer = CreateDemoBuffer( false, params );
  479. }
  480. if ( !m_pBuffer || !m_pBuffer->IsInitialized() )
  481. {
  482. ConMsg( "CDemoFile::Open: couldn't open file %s for %s.\n",
  483. name, bReadOnly ? "reading" : "writing" );
  484. Close();
  485. return false;
  486. }
  487. Q_strncpy( m_szFileName, name, sizeof(m_szFileName) );
  488. return true;
  489. }
  490. bool CDemoFile::IsOpen()
  491. {
  492. return m_pBuffer && m_pBuffer->IsInitialized();
  493. }
  494. void CDemoFile::Close()
  495. {
  496. delete m_pBuffer;
  497. m_pBuffer = NULL;
  498. }
  499. int CDemoFile::GetSize()
  500. {
  501. return m_pBuffer->TellMaxPut();
  502. }
  503. void CDemoFile::DumpBufferToFile( char const* pFilename, const demoheader_t &header )
  504. {
  505. // TODO: Fix this crash - when user tries to download before replay_record has been run.
  506. m_pBuffer->DumpToFile( pFilename, header );
  507. }
  508. void CDemoFile::UpdateStartTick( int& nStartTick )
  509. {
  510. if ( m_pBuffer )
  511. {
  512. m_pBuffer->UpdateStartTick( nStartTick );
  513. }
  514. }
  515. void CDemoFile::NotifySignonComplete()
  516. {
  517. m_pBuffer->NotifySignonComplete();
  518. }
  519. void CDemoFile::NotifyBeginFrame()
  520. {
  521. m_pBuffer->NotifyBeginFrame();
  522. }
  523. void CDemoFile::NotifyEndFrame()
  524. {
  525. m_pBuffer->NotifyEndFrame();
  526. }