Richard Boegli's CnC_Generals_Zero_Hour Fork WIP
This is documentation of Richard Boegil's Zero Hour Fork
 
Loading...
Searching...
No Matches
mixfile.cpp
Go to the documentation of this file.
1/*
2** Command & Conquer Generals Zero Hour(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/***********************************************************************************************
20 *** Confidential - Westwood Studios ***
21 ***********************************************************************************************
22 * *
23 * Project Name : Commando *
24 * *
25 * $Archive:: /Commando/Code/wwlib/mixfile.cpp $*
26 * *
27 * $Author:: Patrick $*
28 * *
29 * $Modtime:: 9/12/01 7:39p $*
30 * *
31 * $Revision:: 4 $*
32 * *
33 *---------------------------------------------------------------------------------------------*
34 * Functions: *
35 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
36
37#include "mixfile.h"
38#include "wwdebug.h"
39#include "ffactory.h"
40#include "wwfile.h"
41#include "realcrc.h"
42#include "rawfile.h"
43#include "win.h"
44#include "bittype.h"
45
46/*
47**
48*/
49typedef struct
50{
51 char signature[4];
54
56
57typedef struct
58{
60
62
64 bool operator== (const FileOffsetStruct &src) { return false; }
65 bool operator!= (const FileOffsetStruct &src) { return true; }
66
68 unsigned long Offset;
69};
70
71/*
72**
73*/
74MixFileFactoryClass::MixFileFactoryClass( const char * mix_filename, FileFactoryClass * factory ) :
75 FileCount (0),
76 NamesOffset (0),
77 IsValid (false),
78 BaseOffset (0),
79 Factory (NULL),
80 IsModified (false)
81{
82// WWDEBUG_SAY(( "MixFileFactory( %s )\n", mix_filename ));
83 MixFilename = mix_filename;
84 Factory = factory;
85 FilenameList.Set_Growth_Step (1000);
86
87 // First, open the mix file
88 FileClass * file = factory->Get_File( mix_filename );
89
90// WWASSERT( file );
91
92 if ( file && file->Is_Available() ) {
93
94 file->Open();
95
96 IsValid = true;
97
98 //
99 // Read the file header
100 //
101 MIXFILE_HEADER header = { 0 };
102 IsValid = (file->Read( &header, sizeof( header ) ) == sizeof( header ));
103
104 //
105 // Validate the file header
106 //
107 if ( IsValid ) {
108 IsValid = (::memcmp( header.signature, "MIX1", sizeof ( header.signature ) ) == 0);
109 }
110
111 //
112 // Seek to the data start
113 //
114 FileCount = 0;
115 if ( IsValid ) {
116 file->Seek( header.header_offset, SEEK_SET );
117 IsValid = ( file->Read( &FileCount, sizeof( FileCount ) ) == sizeof( FileCount ) );
118 }
119
120 //
121 // Read the array of data headers
122 //
123 if ( IsValid ) {
124 FileInfo.Resize( FileCount );
125 int size = FileCount * sizeof( FileInfoStruct );
126 IsValid = ( file->Read( &FileInfo[0], size ) == size );
127 }
128
129 //
130 // Check for success
131 //
132 if ( IsValid ) {
133 BaseOffset = 0;
134 NamesOffset = header.names_offset;
135 WWDEBUG_SAY(( "MixFileFactory( %s ) loaded successfully %d files\n", MixFilename, FileInfo.Length() ));
136 } else {
137 FileInfo.Resize(0);
138 }
139
140 factory->Return_File( file );
141
142 } else {
143 WWDEBUG_SAY(( "MixFileFactory( %s ) FAILED\n", mix_filename ));
144 }
145}
146
148{
149 FileInfo.Resize(0);
150}
151
153{
154 if (IsValid == false) {
155 return false;
156 }
157
158 bool retval = false;
159
160 //
161 // Attempt to open the file
162 //
163 RawFileClass *file = (RawFileClass *)Factory->Get_File( MixFilename );
164 if ( file != NULL && file->Open ( RawFileClass::READ ) ) {
165
166 //
167 // Seek to the names offset header
168 //
169 file->Seek (NamesOffset, SEEK_SET);
170 retval = true;
171
172 //
173 // Read the count of files
174 //
175 int file_count = 0;
176 if (file->Read( &file_count, sizeof( file_count) ) == sizeof( file_count )) {
177
178 //
179 // Loop over each saved filename
180 //
181 bool keep_going = true;
182 for (int index = 0; index < file_count && keep_going; index ++) {
183 keep_going = false;
184
185 //
186 // Get the length of the filename
187 //
188 uint8 name_len = 0;
189 if (file->Read( &name_len, sizeof( name_len ) ) == sizeof( name_len )) {
190
191 //
192 // Read the filename
193 //
194 StringClass filename;
195 if (file->Read( filename.Get_Buffer( name_len ), name_len ) == name_len ) {
196
197 //
198 // Add the filename to our list
199 //
200 list.Add( filename );
201 keep_going = true;
202 }
203 }
204 }
205 }
206
207 //
208 // Close the file
209 //
210 Factory->Return_File( file );
211 }
212
213 return retval;
214}
215
217{
218 if ( FileInfo.Length() == 0 ) {
219 return NULL;
220 }
221// WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s )\n", filename ));
222
223 RawFileClass *file = NULL;
224
225 // Create the key block that will be used to binary search for the file.
226 unsigned long crc = CRC_Stringi( filename );
227
228 // Binary search for the file in this mixfile. If it is found, then create the file
229 FileInfoStruct * info = NULL;
230 FileInfoStruct * base = &FileInfo[0];
231 int stride = FileInfo.Length();
232 while (stride > 0) {
233 int pivot = stride / 2;
234 FileInfoStruct * tryptr = base + pivot;
235 if (crc < tryptr->CRC) {
236 stride = pivot;
237 } else {
238 if (tryptr->CRC == crc) {
239 info = tryptr;
240 break;
241 }
242 base = tryptr + 1;
243 stride -= pivot + 1;
244 }
245 }
246
247 if ( info != NULL) {
248// WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename ));
249 file = (RawFileClass *)Factory->Get_File( MixFilename );
250 if ( file ) {
251 file->Bias( BaseOffset + info->Offset, info->Size );
252 }
253// WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename ));
254 } else {
255// WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) NOT FOUND\n", filename ));
256 }
257
258 return file;
259}
260
262{
263 if ( file != NULL ) {
264 Factory->Return_File( file );
265 }
266}
267
268
269/*
270**
271*/
272void
273MixFileFactoryClass::Add_File (const char *full_path, const char *filename)
274{
275 AddInfoStruct info;
276 info.FullPath = full_path;
277 info.Filename = filename;
278 PendingAddFileList.Add (info);
279 IsModified = true;
280 return ;
281}
282
283
284/*
285**
286*/
287void
289{
290 //
291 // Remove this file (if it exists) from our filename list
292 //
293 for (int list_index = 0; list_index < FilenameList.Count (); list_index ++) {
294 if (FilenameList[list_index].Compare_No_Case (filename) == 0) {
295 FilenameList.Delete (list_index);
296 IsModified = true;
297 break;
298 }
299 }
300
301 return ;
302}
303
304
305/*
306**
307*/
308void
310{
311 //
312 // Exit if there's nothing to do.
313 //
314 if (IsModified == false) {
315 return ;
316 }
317
318 //
319 // Get the path of the mix file
320 //
321 char drive[_MAX_DRIVE] = { 0 };
322 char dir[_MAX_DIR] = { 0 };
323 ::_splitpath (MixFilename, drive, dir, NULL, NULL);
324 StringClass path = drive;
325 path += dir;
326
327 //
328 // Try to find a temp filename
329 //
330 StringClass full_path;
331 if (Get_Temp_Filename (path, full_path)) {
332 MixFileCreator new_mix_file (full_path);
333
334 //
335 // Add all the remaining files from our file set
336 //
337 for (int index = 0; index < FilenameList.Count (); index ++) {
338 StringClass &filename = FilenameList[index];
339
340 //
341 // Copy this file data to the mix file
342 //
343 FileClass *file_data = Get_File (filename);
344 if (file_data != NULL) {
345 file_data->Open ();
346 new_mix_file.Add_File (filename, file_data);
347 Return_File (file_data);
348
349 //
350 // Remove this file from the pending list (if necessary)
351 //
352 for (int temp_index = 0; temp_index < PendingAddFileList.Count (); temp_index ++) {
353 if (filename.Compare_No_Case (PendingAddFileList[temp_index].Filename) == 0) {
354 PendingAddFileList.Delete (temp_index);
355 break;
356 }
357 }
358 }
359 }
360
361 //
362 // Add the new files that are pending
363 //
364 for (index = 0; index < PendingAddFileList.Count (); index ++) {
365 new_mix_file.Add_File (PendingAddFileList[index].FullPath, PendingAddFileList[index].Filename);
366 }
367 }
368
369 //
370 // Delete the old mix file and rename the new one
371 //
372 ::DeleteFile (MixFilename);
373 ::MoveFile (full_path, MixFilename);
374
375 //
376 // Reset the lists
377 //
378 IsModified = false;
379 PendingAddFileList.Delete_All ();
380 return ;
381}
382
383
384/*
385**
386*/
387bool
388MixFileFactoryClass::Get_Temp_Filename (const char *path, StringClass &full_path)
389{
390 bool retval = false;
391
392 StringClass temp_path = path;
393 temp_path += "_tmpmix";
394
395 //
396 // Try to find a unique temp filename
397 //
398 for (int index = 0; index < 20; index ++) {
399 full_path.Format ("%s%.2d.dat", (const char *)temp_path, index + 1);
400 if (GetFileAttributes (full_path) == 0xFFFFFFFF) {
401 retval = true;
402 break;
403 }
404 }
405
406 return retval;
407}
408
409
410//
411// Comparison function, used by Build_Ordered_Filename_List
412//
413int MixFileFactoryClass::File_Offset_Compare(const void * a, const void * b)
414{
415 unsigned int OffsetA = ((FileOffsetStruct*)a)->Offset;
416 unsigned int OffsetB = ((FileOffsetStruct*)b)->Offset;
417 if ( OffsetA < OffsetB ) return -1;
418 if ( OffsetA > OffsetB ) return 1;
419 return 0;
420}
421
422
423//
424// Function builds a list of file names in the order that they are stored in the file
425//
427{
428 if (IsValid == false) {
429 return false;
430 }
431
432 // get list of filenames
434 if (!Build_Filename_List(name_list)) {
435 return false;
436 }
437
438 // associate offset with each name and add to list
440 local_file_info.Resize( name_list.Count());
441 for (int i = 0; i < name_list.Count(); ++i) {
442 // Here, we have to assume that the names in the list are in CRC order, just like FileInfo is.
443 FileOffsetStruct temp;
444 temp.Filename = name_list[i];
445 temp.Offset = FileInfo[i].Offset;
446 local_file_info.Add( temp );
447 }
448
449 // sort name/offset by offset
450 if (local_file_info.Count() > 1) {
451 qsort( &local_file_info[0], local_file_info.Count(), sizeof(local_file_info[0]), &File_Offset_Compare);
452 }
453
454 // add names to output parameter
455 list.Clear();
456 list.Resize( name_list.Count());
457 for (i = 0; i < local_file_info.Count(); ++i) {
458 list.Add(local_file_info[i].Filename);
459 }
460
461 return true;
462}
463
464/*
465**
466*/
468
469MixFileCreator::MixFileCreator( const char * filename )
470{
471 WWDEBUG_SAY(( "Creating Mix File %s\n", filename ));
472
473 MixFile = _SimpleFileFactory.Get_File(filename);
474 if ( MixFile != NULL ) {
475 MixFile->Open( FileClass::WRITE );
476 MixFile->Write( "MIX1", 4 );
477 long header_offset = 0;
478 MixFile->Write( &header_offset, sizeof( header_offset ) );
479 long names_offset = 0;
480 MixFile->Write( &names_offset, sizeof( names_offset ) );
481 long unused = 0;
482 MixFile->Write( &unused, sizeof( unused ) );
483 }
484}
485
486int MixFileCreator::File_Info_Compare(const void * a, const void * b)
487{
488 unsigned int CRCA = ((FileInfoStruct*)a)->CRC;
489 unsigned int CRCB = ((FileInfoStruct*)b)->CRC;
490 if ( CRCA < CRCB ) return -1;
491 if ( CRCA > CRCB ) return 1;
492 return 0;
493// return ((FileInfoStruct*)a)->CRC - ((FileInfoStruct*)b)->CRC;
494}
495
497{
498 if ( MixFile != NULL ) {
499
500 // Save Header Data
501 int header_offset = MixFile->Tell();
502
503 // Save file count
504 int i,num_files = FileInfo.Count();
505 WWDEBUG_SAY(( "Closing with %d files\n", num_files ));
506 MixFile->Write( &num_files, sizeof( num_files ) );
507
508 if ( num_files > 1 ) {
509 qsort( &FileInfo[0], num_files, sizeof(FileInfo[0]), &File_Info_Compare);
510 }
511
512 // Save file info (CRC, Offset, Size )
513 for ( i = 0; i < num_files; i++ ) {
514 MixFile->Write( &FileInfo[i].CRC, 4 );
515 MixFile->Write( &FileInfo[i].Offset, 4 );
516 MixFile->Write( &FileInfo[i].Size, 4 );
517// WWDEBUG_SAY(( "Write CRC %08X\n", FileInfo[i].CRC ));
518 }
519
520 // ---------------------------------------
521
522 // Save Names Data
523 int names_offset = MixFile->Tell();
524
525 // Save file count
526 MixFile->Write( &num_files, sizeof( num_files ) );
527
528 // Save file info
529 for ( i = 0; i < num_files; i++ ) {
530 const char * filename = FileInfo[i].Filename;
531 int size = FileInfo[i].Filename.Get_Length()+1;
532 WWASSERT( size < 255 );
533 unsigned char csize = size;
534 MixFile->Write( &csize, 1 );
535 MixFile->Write( filename, size );
536 }
537
538 // ---------------------------------------
539
540 MixFile->Seek( 4, SEEK_SET );
541
542 // Save header offset
543 WWDEBUG_SAY(( "Writing header offset %d (%08X)\n", header_offset, header_offset ));
544 MixFile->Write( &header_offset, sizeof( header_offset ) );
545
546 // Save names offset
547 WWDEBUG_SAY(( "Writing names offset %d (%08X)\n", names_offset, names_offset ));
548 MixFile->Write( &names_offset, sizeof( names_offset ) );
549
550 // ---------------------------------------
551
552 MixFile->Close();
553
554 _SimpleFileFactory.Return_File(MixFile);
555 }
556}
557
558void MixFileCreator::Add_File( const char * source_filename, const char * saved_filename )
559{
560 if ( saved_filename == NULL ) {
561 saved_filename = source_filename;
562 }
563
564 if ( MixFile != NULL ) {
565
566 FileClass * file = _SimpleFileFactory.Get_File( source_filename );
567
568 if ( file && file->Is_Available() ) {
569
570 file->Open();
571
572 MixFileCreator::FileInfoStruct info;
573 info.CRC = CRC_Stringi( saved_filename );
574 info.Offset = MixFile->Tell();
575 info.Size = file->Size();
576 FileInfo.Add( info );
577 FileInfo[ FileInfo.Count()-1 ].Filename = saved_filename;
578
579 WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n",
580 saved_filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size ));
581
582 int size = file->Size();
583 while ( size ) {
584 char buffer[ 4096 ];
585 int amount = MIN( sizeof( buffer ), size );
586 size -= amount;
587 file->Read( buffer, amount );
588 if ( MixFile->Write( buffer, amount ) != amount ) {
589 WWDEBUG_SAY(( "Failed to write MixFile\n" ));
590 }
591 }
592
593 // Pad the MixFile to make DWord Aligned
594 int offset = MixFile->Tell();
595 offset = (8-(offset & 7)) & 7;
596 if ( offset != 0 ) {
597 char zeros[8] = {0,0,0,0,0,0,0,0};
598 if ( MixFile->Write( zeros, offset ) != offset ) {
599 WWDEBUG_SAY(( "Failed to write padding\n" ));
600 }
601 }
602
603 file->Close();
604 _SimpleFileFactory.Return_File( file );
605
606 } else {
607 WWDEBUG_SAY(( "MixFileCreator::Failed to open \"%s\"\n", source_filename ));
608 }
609 }
610}
611
612
613void MixFileCreator::Add_File( const char * filename, FileClass *file )
614{
615 if ( MixFile != NULL ) {
616
617 MixFileCreator::FileInfoStruct info;
618 info.CRC = CRC_Stringi( filename );
619 info.Offset = MixFile->Tell();
620 info.Size = file->Size();
621 FileInfo.Add( info );
622 FileInfo[ FileInfo.Count()-1 ].Filename = filename;
623
624 WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n",
625 filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size ));
626
627 int size = file->Size();
628 while ( size ) {
629 char buffer[ 4096 ];
630 int amount = MIN( sizeof( buffer ), size );
631 size -= amount;
632 file->Read( buffer, amount );
633 if ( MixFile->Write( buffer, amount ) != amount ) {
634 WWDEBUG_SAY(( "Failed to write MixFile\n" ));
635 }
636 }
637
638 // Pad the MixFile to make DWord Aligned
639 int offset = MixFile->Tell();
640 offset = (8-(offset & 7)) & 7;
641 if ( offset != 0 ) {
642 char zeros[8] = {0,0,0,0,0,0,0,0};
643 if ( MixFile->Write( zeros, offset ) != offset ) {
644 WWDEBUG_SAY(( "Failed to write padding\n" ));
645 }
646 }
647 }
648
649 return ;
650}
651
652
653/*
654**
655*/
656void Add_Files( const char * dir, MixFileCreator & mix )
657{
658 BOOL bcontinue = TRUE;
659 HANDLE hfile_find;
660 WIN32_FIND_DATA find_info = {0};
661 StringClass path;
662 path.Format( "data\\makemix\\%s*.*", dir );
663 WWDEBUG_SAY(( "Adding files from %s\n", path ));
664
665 for (hfile_find = ::FindFirstFile( path, &find_info);
666 (hfile_find != INVALID_HANDLE_VALUE) && bcontinue;
667 bcontinue = ::FindNextFile(hfile_find, &find_info)) {
668 if ( find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
669 if ( find_info.cFileName[0] != '.' ) {
670 StringClass path;
671 path.Format( "%s%s\\", dir, find_info.cFileName );
672 Add_Files( path, mix );
673 }
674 } else {
675 StringClass name;
676 name.Format( "%s%s", dir, find_info.cFileName );
677 StringClass source;
678 source.Format( "makemix\\%s", name );
679 mix.Add_File( source, name );
680// WWDEBUG_SAY(( "Adding file from %s %s\n", source, name ));
681 }
682 }
683}
684
685void Setup_Mix_File( void )
686{
687 _SimpleFileFactory.Set_Sub_Directory( "DATA\\" );
688// _SimpleFileFactory.Set_Strip_Path( true );
689
690 WWDEBUG_SAY(( "Mix File Create .....\n" ));
691
692 {
693 MixFileCreator mix( "MAKEMIX.MIX" );
694 Add_Files( "", mix );
695 }
696
697}
#define NULL
Definition BaseType.h:92
#define TRUE
Definition BaseType.h:109
if(pDbg)
#define WWASSERT
#define SEEK_SET
Definition WWFILE.H:55
#define MIN(a, b)
Definition always.h:189
unsigned char uint8
Definition bittype.h:44
@ false
Definition bool.h:59
unsigned long CRC_Stringi(const char *string, unsigned long crc)
Definition realcrc.cpp:168
#define BOOL
Definition Wnd_File.h:57
char drive[_MAX_DRIVE]
Definition autorun.cpp:214
char dir[_MAX_DIR]
Definition autorun.cpp:215
Definition crc.h:59
virtual bool Resize(int newsize, T const *array=0)
Definition Vector.H:615
int Count(void) const
Definition Vector.H:507
virtual void Clear(void)
Definition Vector.H:500
bool Add(T const &object)
Definition Vector.H:671
virtual int Seek(int pos, int dir=SEEK_CUR)=0
virtual int Size(void)=0
virtual bool Is_Available(int forced=false)=0
virtual int Read(void *buffer, int size)=0
virtual void Close(void)=0
virtual int Open(char const *filename, int rights=READ)=0
@ WRITE
Definition WWFILE.H:72
virtual void Return_File(FileClass *file)=0
virtual FileClass * Get_File(char const *filename)=0
MixFileCreator(const char *filename)
Definition mixfile.cpp:469
~MixFileCreator(void)
Definition mixfile.cpp:496
void Add_File(const char *source_filename, const char *saved_filename=NULL)
Definition mixfile.cpp:558
void Delete_File(const char *filename)
Definition mixfile.cpp:288
virtual void Return_File(FileClass *file)
Definition mixfile.cpp:261
void Flush_Changes(void)
Definition mixfile.cpp:309
virtual FileClass * Get_File(char const *filename)
Definition mixfile.cpp:216
bool Build_Ordered_Filename_List(DynamicVectorClass< StringClass > &list)
Definition mixfile.cpp:426
void Add_File(const char *full_path, const char *filename)
Definition mixfile.cpp:273
bool Build_Filename_List(DynamicVectorClass< StringClass > &list)
Definition mixfile.cpp:152
virtual ~MixFileFactoryClass(void)
Definition mixfile.cpp:147
MixFileFactoryClass(const char *mix_filename, FileFactoryClass *factory)
Definition mixfile.cpp:74
virtual void Bias(int start, int length=-1)
Definition rawfile.cpp:1130
virtual int Open(char const *filename, int rights=READ)
Definition rawfile.cpp:356
virtual int Seek(int pos, int dir=SEEK_CUR)
Definition rawfile.cpp:773
virtual int Read(void *buffer, int size)
Definition rawfile.cpp:614
int _cdecl Format(const TCHAR *format,...)
Definition wwstring.cpp:273
TCHAR * Get_Buffer(int new_length)
Definition wwstring.h:549
int Compare_No_Case(const TCHAR *string) const
Definition wwstring.h:378
void Add_Files(const char *dir, MixFileCreator &mix)
Definition mixfile.cpp:656
void Setup_Mix_File(void)
Definition mixfile.cpp:685
SimpleFileFactoryClass _SimpleFileFactory
Definition mixfile.cpp:467
else return(RetVal)
bool operator!=(const FileOffsetStruct &src)
Definition mixfile.cpp:65
unsigned long Offset
Definition mixfile.cpp:68
bool operator==(const FileOffsetStruct &src)
Definition mixfile.cpp:64
StringClass Filename
Definition mixfile.cpp:67
char signature[4]
Definition mixfile.cpp:51
long header_offset
Definition mixfile.cpp:52
long names_offset
Definition mixfile.cpp:53
#define WWDEBUG_SAY(x)
Definition wwdebug.h:114