mixfile.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** Confidential - Westwood Studios ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : Commando *
  23. * *
  24. * $Archive:: /Commando/Code/wwlib/mixfile.cpp $*
  25. * *
  26. * $Author:: Patrick $*
  27. * *
  28. * $Modtime:: 9/12/01 7:39p $*
  29. * *
  30. * $Revision:: 4 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "mixfile.h"
  36. #include "wwdebug.h"
  37. #include "ffactory.h"
  38. #include "wwfile.h"
  39. #include "realcrc.h"
  40. #include "rawfile.h"
  41. #include "win.h"
  42. #include "bittype.h"
  43. /*
  44. **
  45. */
  46. typedef struct
  47. {
  48. char signature[4];
  49. long header_offset;
  50. long names_offset;
  51. } MIXFILE_HEADER;
  52. typedef struct
  53. {
  54. long file_count;
  55. } MIXFILE_DATA_HEADER;
  56. /*
  57. **
  58. */
  59. MixFileFactoryClass::MixFileFactoryClass( const char * mix_filename, FileFactoryClass * factory ) :
  60. FileCount (0),
  61. NamesOffset (0),
  62. IsValid (false),
  63. BaseOffset (0),
  64. Factory (NULL),
  65. IsModified (false)
  66. {
  67. // WWDEBUG_SAY(( "MixFileFactory( %s )\n", mix_filename ));
  68. MixFilename = mix_filename;
  69. Factory = factory;
  70. FilenameList.Set_Growth_Step (1000);
  71. // First, open the mix file
  72. FileClass * file = factory->Get_File( mix_filename );
  73. // WWASSERT( file );
  74. if ( file && file->Is_Available() ) {
  75. file->Open();
  76. IsValid = true;
  77. //
  78. // Read the file header
  79. //
  80. MIXFILE_HEADER header = { 0 };
  81. IsValid = (file->Read( &header, sizeof( header ) ) == sizeof( header ));
  82. //
  83. // Validate the file header
  84. //
  85. if ( IsValid ) {
  86. IsValid = (::memcmp( header.signature, "MIX1", sizeof ( header.signature ) ) == 0);
  87. }
  88. //
  89. // Seek to the data start
  90. //
  91. FileCount = 0;
  92. if ( IsValid ) {
  93. file->Seek( header.header_offset, SEEK_SET );
  94. IsValid = ( file->Read( &FileCount, sizeof( FileCount ) ) == sizeof( FileCount ) );
  95. }
  96. //
  97. // Read the array of data headers
  98. //
  99. if ( IsValid ) {
  100. FileInfo.Resize( FileCount );
  101. int size = FileCount * sizeof( FileInfoStruct );
  102. IsValid = ( file->Read( &FileInfo[0], size ) == size );
  103. }
  104. //
  105. // Check for success
  106. //
  107. if ( IsValid ) {
  108. BaseOffset = 0;
  109. NamesOffset = header.names_offset;
  110. WWDEBUG_SAY(( "MixFileFactory( %s ) loaded successfully %d files\n", MixFilename, FileInfo.Length() ));
  111. } else {
  112. FileInfo.Resize(0);
  113. }
  114. factory->Return_File( file );
  115. } else {
  116. WWDEBUG_SAY(( "MixFileFactory( %s ) FAILED\n", mix_filename ));
  117. }
  118. }
  119. MixFileFactoryClass::~MixFileFactoryClass( void )
  120. {
  121. FileInfo.Resize(0);
  122. }
  123. bool MixFileFactoryClass::Build_Filename_List (DynamicVectorClass<StringClass> &list)
  124. {
  125. if (IsValid == false) {
  126. return false;
  127. }
  128. bool retval = false;
  129. //
  130. // Attempt to open the file
  131. //
  132. RawFileClass *file = (RawFileClass *)Factory->Get_File( MixFilename );
  133. if ( file != NULL && file->Open ( RawFileClass::READ ) ) {
  134. //
  135. // Seek to the names offset header
  136. //
  137. file->Seek (NamesOffset, SEEK_SET);
  138. retval = true;
  139. //
  140. // Read the count of files
  141. //
  142. int file_count = 0;
  143. if (file->Read( &file_count, sizeof( file_count) ) == sizeof( file_count )) {
  144. //
  145. // Loop over each saved filename
  146. //
  147. bool keep_going = true;
  148. for (int index = 0; index < file_count && keep_going; index ++) {
  149. keep_going = false;
  150. //
  151. // Get the length of the filename
  152. //
  153. uint8 name_len = 0;
  154. if (file->Read( &name_len, sizeof( name_len ) ) == sizeof( name_len )) {
  155. //
  156. // Read the filename
  157. //
  158. StringClass filename;
  159. if (file->Read( filename.Get_Buffer( name_len ), name_len ) == name_len ) {
  160. //
  161. // Add the filename to our list
  162. //
  163. list.Add( filename );
  164. keep_going = true;
  165. }
  166. }
  167. }
  168. }
  169. //
  170. // Close the file
  171. //
  172. Factory->Return_File( file );
  173. }
  174. return retval;
  175. }
  176. FileClass * MixFileFactoryClass::Get_File( char const *filename )
  177. {
  178. if ( FileInfo.Length() == 0 ) {
  179. return NULL;
  180. }
  181. // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s )\n", filename ));
  182. RawFileClass *file = NULL;
  183. // Create the key block that will be used to binary search for the file.
  184. unsigned long crc = CRC_Stringi( filename );
  185. // Binary search for the file in this mixfile. If it is found, then create the file
  186. FileInfoStruct * info = NULL;
  187. FileInfoStruct * base = &FileInfo[0];
  188. int stride = FileInfo.Length();
  189. while (stride > 0) {
  190. int pivot = stride / 2;
  191. FileInfoStruct * tryptr = base + pivot;
  192. if (crc < tryptr->CRC) {
  193. stride = pivot;
  194. } else {
  195. if (tryptr->CRC == crc) {
  196. info = tryptr;
  197. break;
  198. }
  199. base = tryptr + 1;
  200. stride -= pivot + 1;
  201. }
  202. }
  203. if ( info != NULL) {
  204. // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename ));
  205. file = (RawFileClass *)Factory->Get_File( MixFilename );
  206. if ( file ) {
  207. file->Bias( BaseOffset + info->Offset, info->Size );
  208. }
  209. // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename ));
  210. } else {
  211. // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) NOT FOUND\n", filename ));
  212. }
  213. return file;
  214. }
  215. void MixFileFactoryClass::Return_File( FileClass * file )
  216. {
  217. if ( file != NULL ) {
  218. Factory->Return_File( file );
  219. }
  220. }
  221. /*
  222. **
  223. */
  224. void
  225. MixFileFactoryClass::Add_File (const char *full_path, const char *filename)
  226. {
  227. AddInfoStruct info;
  228. info.FullPath = full_path;
  229. info.Filename = filename;
  230. PendingAddFileList.Add (info);
  231. IsModified = true;
  232. return ;
  233. }
  234. /*
  235. **
  236. */
  237. void
  238. MixFileFactoryClass::Delete_File (const char *filename)
  239. {
  240. //
  241. // Remove this file (if it exists) from our filename list
  242. //
  243. for (int list_index = 0; list_index < FilenameList.Count (); list_index ++) {
  244. if (FilenameList[list_index].Compare_No_Case (filename) == 0) {
  245. FilenameList.Delete (list_index);
  246. IsModified = true;
  247. break;
  248. }
  249. }
  250. return ;
  251. }
  252. /*
  253. **
  254. */
  255. void
  256. MixFileFactoryClass::Flush_Changes (void)
  257. {
  258. //
  259. // Exit if there's nothing to do.
  260. //
  261. if (IsModified == false) {
  262. return ;
  263. }
  264. //
  265. // Get the path of the mix file
  266. //
  267. char drive[_MAX_DRIVE] = { 0 };
  268. char dir[_MAX_DIR] = { 0 };
  269. ::_splitpath (MixFilename, drive, dir, NULL, NULL);
  270. StringClass path = drive;
  271. path += dir;
  272. //
  273. // Try to find a temp filename
  274. //
  275. StringClass full_path;
  276. if (Get_Temp_Filename (path, full_path)) {
  277. MixFileCreator new_mix_file (full_path);
  278. //
  279. // Add all the remaining files from our file set
  280. //
  281. for (int index = 0; index < FilenameList.Count (); index ++) {
  282. StringClass &filename = FilenameList[index];
  283. //
  284. // Copy this file data to the mix file
  285. //
  286. FileClass *file_data = Get_File (filename);
  287. if (file_data != NULL) {
  288. file_data->Open ();
  289. new_mix_file.Add_File (filename, file_data);
  290. Return_File (file_data);
  291. //
  292. // Remove this file from the pending list (if necessary)
  293. //
  294. for (int temp_index = 0; temp_index < PendingAddFileList.Count (); temp_index ++) {
  295. if (filename.Compare_No_Case (PendingAddFileList[temp_index].Filename) == 0) {
  296. PendingAddFileList.Delete (temp_index);
  297. break;
  298. }
  299. }
  300. }
  301. }
  302. //
  303. // Add the new files that are pending
  304. //
  305. for (index = 0; index < PendingAddFileList.Count (); index ++) {
  306. new_mix_file.Add_File (PendingAddFileList[index].FullPath, PendingAddFileList[index].Filename);
  307. }
  308. }
  309. //
  310. // Delete the old mix file and rename the new one
  311. //
  312. ::DeleteFile (MixFilename);
  313. ::MoveFile (full_path, MixFilename);
  314. //
  315. // Reset the lists
  316. //
  317. IsModified = false;
  318. PendingAddFileList.Delete_All ();
  319. return ;
  320. }
  321. /*
  322. **
  323. */
  324. bool
  325. MixFileFactoryClass::Get_Temp_Filename (const char *path, StringClass &full_path)
  326. {
  327. bool retval = false;
  328. StringClass temp_path = path;
  329. temp_path += "_tmpmix";
  330. //
  331. // Try to find a unique temp filename
  332. //
  333. for (int index = 0; index < 20; index ++) {
  334. full_path.Format ("%s%.2d.dat", (const char *)temp_path, index + 1);
  335. if (GetFileAttributes (full_path) == 0xFFFFFFFF) {
  336. retval = true;
  337. break;
  338. }
  339. }
  340. return retval;
  341. }
  342. /*
  343. **
  344. */
  345. SimpleFileFactoryClass _SimpleFileFactory;
  346. MixFileCreator::MixFileCreator( const char * filename )
  347. {
  348. WWDEBUG_SAY(( "Creating Mix File %s\n", filename ));
  349. MixFile = _SimpleFileFactory.Get_File(filename);
  350. if ( MixFile != NULL ) {
  351. MixFile->Open( FileClass::WRITE );
  352. MixFile->Write( "MIX1", 4 );
  353. long header_offset = 0;
  354. MixFile->Write( &header_offset, sizeof( header_offset ) );
  355. long names_offset = 0;
  356. MixFile->Write( &names_offset, sizeof( names_offset ) );
  357. long unused = 0;
  358. MixFile->Write( &unused, sizeof( unused ) );
  359. }
  360. }
  361. int MixFileCreator::File_Info_Compare(const void * a, const void * b)
  362. {
  363. unsigned int CRCA = ((FileInfoStruct*)a)->CRC;
  364. unsigned int CRCB = ((FileInfoStruct*)b)->CRC;
  365. if ( CRCA < CRCB ) return -1;
  366. if ( CRCA > CRCB ) return 1;
  367. return 0;
  368. // return ((FileInfoStruct*)a)->CRC - ((FileInfoStruct*)b)->CRC;
  369. }
  370. MixFileCreator::~MixFileCreator( void )
  371. {
  372. if ( MixFile != NULL ) {
  373. // Save Header Data
  374. int header_offset = MixFile->Tell();
  375. // Save file count
  376. int i,num_files = FileInfo.Count();
  377. WWDEBUG_SAY(( "Closing with %d files\n", num_files ));
  378. MixFile->Write( &num_files, sizeof( num_files ) );
  379. if ( num_files > 1 ) {
  380. qsort( &FileInfo[0], num_files, sizeof(FileInfo[0]), &File_Info_Compare);
  381. }
  382. // Save file info (CRC, Offset, Size )
  383. for ( i = 0; i < num_files; i++ ) {
  384. MixFile->Write( &FileInfo[i].CRC, 4 );
  385. MixFile->Write( &FileInfo[i].Offset, 4 );
  386. MixFile->Write( &FileInfo[i].Size, 4 );
  387. // WWDEBUG_SAY(( "Write CRC %08X\n", FileInfo[i].CRC ));
  388. }
  389. // ---------------------------------------
  390. // Save Names Data
  391. int names_offset = MixFile->Tell();
  392. // Save file count
  393. MixFile->Write( &num_files, sizeof( num_files ) );
  394. // Save file info
  395. for ( i = 0; i < num_files; i++ ) {
  396. const char * filename = FileInfo[i].Filename;
  397. int size = FileInfo[i].Filename.Get_Length()+1;
  398. WWASSERT( size < 255 );
  399. unsigned char csize = size;
  400. MixFile->Write( &csize, 1 );
  401. MixFile->Write( filename, size );
  402. }
  403. // ---------------------------------------
  404. MixFile->Seek( 4, SEEK_SET );
  405. // Save header offset
  406. WWDEBUG_SAY(( "Writing header offset %d (%08X)\n", header_offset, header_offset ));
  407. MixFile->Write( &header_offset, sizeof( header_offset ) );
  408. // Save names offset
  409. WWDEBUG_SAY(( "Writing names offset %d (%08X)\n", names_offset, names_offset ));
  410. MixFile->Write( &names_offset, sizeof( names_offset ) );
  411. // ---------------------------------------
  412. MixFile->Close();
  413. _SimpleFileFactory.Return_File(MixFile);
  414. }
  415. }
  416. void MixFileCreator::Add_File( const char * source_filename, const char * saved_filename )
  417. {
  418. if ( saved_filename == NULL ) {
  419. saved_filename = source_filename;
  420. }
  421. if ( MixFile != NULL ) {
  422. FileClass * file = _SimpleFileFactory.Get_File( source_filename );
  423. if ( file && file->Is_Available() ) {
  424. file->Open();
  425. MixFileCreator::FileInfoStruct info;
  426. info.CRC = CRC_Stringi( saved_filename );
  427. info.Offset = MixFile->Tell();
  428. info.Size = file->Size();
  429. FileInfo.Add( info );
  430. FileInfo[ FileInfo.Count()-1 ].Filename = saved_filename;
  431. WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n",
  432. saved_filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size ));
  433. int size = file->Size();
  434. while ( size ) {
  435. char buffer[ 4096 ];
  436. int amount = MIN( sizeof( buffer ), size );
  437. size -= amount;
  438. file->Read( buffer, amount );
  439. if ( MixFile->Write( buffer, amount ) != amount ) {
  440. WWDEBUG_SAY(( "Failed to write MixFile\n" ));
  441. }
  442. }
  443. // Pad the MixFile to make DWord Aligned
  444. int offset = MixFile->Tell();
  445. offset = (8-(offset & 7)) & 7;
  446. if ( offset != 0 ) {
  447. char zeros[8] = {0,0,0,0,0,0,0,0};
  448. if ( MixFile->Write( zeros, offset ) != offset ) {
  449. WWDEBUG_SAY(( "Failed to write padding\n" ));
  450. }
  451. }
  452. file->Close();
  453. _SimpleFileFactory.Return_File( file );
  454. } else {
  455. WWDEBUG_SAY(( "MixFileCreator::Failed to open \"%s\"\n", source_filename ));
  456. }
  457. }
  458. }
  459. void MixFileCreator::Add_File( const char * filename, FileClass *file )
  460. {
  461. if ( MixFile != NULL ) {
  462. MixFileCreator::FileInfoStruct info;
  463. info.CRC = CRC_Stringi( filename );
  464. info.Offset = MixFile->Tell();
  465. info.Size = file->Size();
  466. FileInfo.Add( info );
  467. FileInfo[ FileInfo.Count()-1 ].Filename = filename;
  468. WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n",
  469. filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size ));
  470. int size = file->Size();
  471. while ( size ) {
  472. char buffer[ 4096 ];
  473. int amount = MIN( sizeof( buffer ), size );
  474. size -= amount;
  475. file->Read( buffer, amount );
  476. if ( MixFile->Write( buffer, amount ) != amount ) {
  477. WWDEBUG_SAY(( "Failed to write MixFile\n" ));
  478. }
  479. }
  480. // Pad the MixFile to make DWord Aligned
  481. int offset = MixFile->Tell();
  482. offset = (8-(offset & 7)) & 7;
  483. if ( offset != 0 ) {
  484. char zeros[8] = {0,0,0,0,0,0,0,0};
  485. if ( MixFile->Write( zeros, offset ) != offset ) {
  486. WWDEBUG_SAY(( "Failed to write padding\n" ));
  487. }
  488. }
  489. }
  490. return ;
  491. }
  492. /*
  493. **
  494. */
  495. void Add_Files( const char * dir, MixFileCreator & mix )
  496. {
  497. BOOL bcontinue = TRUE;
  498. HANDLE hfile_find;
  499. WIN32_FIND_DATA find_info = {0};
  500. StringClass path;
  501. path.Format( "data\\makemix\\%s*.*", dir );
  502. WWDEBUG_SAY(( "Adding files from %s\n", path ));
  503. for (hfile_find = ::FindFirstFile( path, &find_info);
  504. (hfile_find != INVALID_HANDLE_VALUE) && bcontinue;
  505. bcontinue = ::FindNextFile(hfile_find, &find_info)) {
  506. if ( find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
  507. if ( find_info.cFileName[0] != '.' ) {
  508. StringClass path;
  509. path.Format( "%s%s\\", dir, find_info.cFileName );
  510. Add_Files( path, mix );
  511. }
  512. } else {
  513. StringClass name;
  514. name.Format( "%s%s", dir, find_info.cFileName );
  515. StringClass source;
  516. source.Format( "makemix\\%s", name );
  517. mix.Add_File( source, name );
  518. // WWDEBUG_SAY(( "Adding file from %s %s\n", source, name ));
  519. }
  520. }
  521. }
  522. void Setup_Mix_File( void )
  523. {
  524. _SimpleFileFactory.Set_Sub_Directory( "DATA\\" );
  525. // _SimpleFileFactory.Set_Strip_Path( true );
  526. WWDEBUG_SAY(( "Mix File Create .....\n" ));
  527. {
  528. MixFileCreator mix( "MAKEMIX.MIX" );
  529. Add_Files( "", mix );
  530. }
  531. }