/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/wwlib/mixfile.cpp $* * * * $Author:: Patrick $* * * * $Modtime:: 9/12/01 7:39p $* * * * $Revision:: 4 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "mixfile.h" #include "wwdebug.h" #include "ffactory.h" #include "wwfile.h" #include "realcrc.h" #include "rawfile.h" #include "win.h" #include "bittype.h" /* ** */ typedef struct { char signature[4]; long header_offset; long names_offset; } MIXFILE_HEADER; typedef struct { long file_count; } MIXFILE_DATA_HEADER; /* ** */ MixFileFactoryClass::MixFileFactoryClass( const char * mix_filename, FileFactoryClass * factory ) : FileCount (0), NamesOffset (0), IsValid (false), BaseOffset (0), Factory (NULL), IsModified (false) { // WWDEBUG_SAY(( "MixFileFactory( %s )\n", mix_filename )); MixFilename = mix_filename; Factory = factory; FilenameList.Set_Growth_Step (1000); // First, open the mix file FileClass * file = factory->Get_File( mix_filename ); // WWASSERT( file ); if ( file && file->Is_Available() ) { file->Open(); IsValid = true; // // Read the file header // MIXFILE_HEADER header = { 0 }; IsValid = (file->Read( &header, sizeof( header ) ) == sizeof( header )); // // Validate the file header // if ( IsValid ) { IsValid = (::memcmp( header.signature, "MIX1", sizeof ( header.signature ) ) == 0); } // // Seek to the data start // FileCount = 0; if ( IsValid ) { file->Seek( header.header_offset, SEEK_SET ); IsValid = ( file->Read( &FileCount, sizeof( FileCount ) ) == sizeof( FileCount ) ); } // // Read the array of data headers // if ( IsValid ) { FileInfo.Resize( FileCount ); int size = FileCount * sizeof( FileInfoStruct ); IsValid = ( file->Read( &FileInfo[0], size ) == size ); } // // Check for success // if ( IsValid ) { BaseOffset = 0; NamesOffset = header.names_offset; WWDEBUG_SAY(( "MixFileFactory( %s ) loaded successfully %d files\n", MixFilename, FileInfo.Length() )); } else { FileInfo.Resize(0); } factory->Return_File( file ); } else { WWDEBUG_SAY(( "MixFileFactory( %s ) FAILED\n", mix_filename )); } } MixFileFactoryClass::~MixFileFactoryClass( void ) { FileInfo.Resize(0); } bool MixFileFactoryClass::Build_Filename_List (DynamicVectorClass &list) { if (IsValid == false) { return false; } bool retval = false; // // Attempt to open the file // RawFileClass *file = (RawFileClass *)Factory->Get_File( MixFilename ); if ( file != NULL && file->Open ( RawFileClass::READ ) ) { // // Seek to the names offset header // file->Seek (NamesOffset, SEEK_SET); retval = true; // // Read the count of files // int file_count = 0; if (file->Read( &file_count, sizeof( file_count) ) == sizeof( file_count )) { // // Loop over each saved filename // bool keep_going = true; for (int index = 0; index < file_count && keep_going; index ++) { keep_going = false; // // Get the length of the filename // uint8 name_len = 0; if (file->Read( &name_len, sizeof( name_len ) ) == sizeof( name_len )) { // // Read the filename // StringClass filename; if (file->Read( filename.Get_Buffer( name_len ), name_len ) == name_len ) { // // Add the filename to our list // list.Add( filename ); keep_going = true; } } } } // // Close the file // Factory->Return_File( file ); } return retval; } FileClass * MixFileFactoryClass::Get_File( char const *filename ) { if ( FileInfo.Length() == 0 ) { return NULL; } // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s )\n", filename )); RawFileClass *file = NULL; // Create the key block that will be used to binary search for the file. unsigned long crc = CRC_Stringi( filename ); // Binary search for the file in this mixfile. If it is found, then create the file FileInfoStruct * info = NULL; FileInfoStruct * base = &FileInfo[0]; int stride = FileInfo.Length(); while (stride > 0) { int pivot = stride / 2; FileInfoStruct * tryptr = base + pivot; if (crc < tryptr->CRC) { stride = pivot; } else { if (tryptr->CRC == crc) { info = tryptr; break; } base = tryptr + 1; stride -= pivot + 1; } } if ( info != NULL) { // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename )); file = (RawFileClass *)Factory->Get_File( MixFilename ); if ( file ) { file->Bias( BaseOffset + info->Offset, info->Size ); } // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) FOUND\n", filename )); } else { // WWDEBUG_SAY(( "MixFileFactoryClass::Get_File( %s ) NOT FOUND\n", filename )); } return file; } void MixFileFactoryClass::Return_File( FileClass * file ) { if ( file != NULL ) { Factory->Return_File( file ); } } /* ** */ void MixFileFactoryClass::Add_File (const char *full_path, const char *filename) { AddInfoStruct info; info.FullPath = full_path; info.Filename = filename; PendingAddFileList.Add (info); IsModified = true; return ; } /* ** */ void MixFileFactoryClass::Delete_File (const char *filename) { // // Remove this file (if it exists) from our filename list // for (int list_index = 0; list_index < FilenameList.Count (); list_index ++) { if (FilenameList[list_index].Compare_No_Case (filename) == 0) { FilenameList.Delete (list_index); IsModified = true; break; } } return ; } /* ** */ void MixFileFactoryClass::Flush_Changes (void) { // // Exit if there's nothing to do. // if (IsModified == false) { return ; } // // Get the path of the mix file // char drive[_MAX_DRIVE] = { 0 }; char dir[_MAX_DIR] = { 0 }; ::_splitpath (MixFilename, drive, dir, NULL, NULL); StringClass path = drive; path += dir; // // Try to find a temp filename // StringClass full_path; if (Get_Temp_Filename (path, full_path)) { MixFileCreator new_mix_file (full_path); // // Add all the remaining files from our file set // for (int index = 0; index < FilenameList.Count (); index ++) { StringClass &filename = FilenameList[index]; // // Copy this file data to the mix file // FileClass *file_data = Get_File (filename); if (file_data != NULL) { file_data->Open (); new_mix_file.Add_File (filename, file_data); Return_File (file_data); // // Remove this file from the pending list (if necessary) // for (int temp_index = 0; temp_index < PendingAddFileList.Count (); temp_index ++) { if (filename.Compare_No_Case (PendingAddFileList[temp_index].Filename) == 0) { PendingAddFileList.Delete (temp_index); break; } } } } // // Add the new files that are pending // for (index = 0; index < PendingAddFileList.Count (); index ++) { new_mix_file.Add_File (PendingAddFileList[index].FullPath, PendingAddFileList[index].Filename); } } // // Delete the old mix file and rename the new one // ::DeleteFile (MixFilename); ::MoveFile (full_path, MixFilename); // // Reset the lists // IsModified = false; PendingAddFileList.Delete_All (); return ; } /* ** */ bool MixFileFactoryClass::Get_Temp_Filename (const char *path, StringClass &full_path) { bool retval = false; StringClass temp_path = path; temp_path += "_tmpmix"; // // Try to find a unique temp filename // for (int index = 0; index < 20; index ++) { full_path.Format ("%s%.2d.dat", (const char *)temp_path, index + 1); if (GetFileAttributes (full_path) == 0xFFFFFFFF) { retval = true; break; } } return retval; } /* ** */ SimpleFileFactoryClass _SimpleFileFactory; MixFileCreator::MixFileCreator( const char * filename ) { WWDEBUG_SAY(( "Creating Mix File %s\n", filename )); MixFile = _SimpleFileFactory.Get_File(filename); if ( MixFile != NULL ) { MixFile->Open( FileClass::WRITE ); MixFile->Write( "MIX1", 4 ); long header_offset = 0; MixFile->Write( &header_offset, sizeof( header_offset ) ); long names_offset = 0; MixFile->Write( &names_offset, sizeof( names_offset ) ); long unused = 0; MixFile->Write( &unused, sizeof( unused ) ); } } int MixFileCreator::File_Info_Compare(const void * a, const void * b) { unsigned int CRCA = ((FileInfoStruct*)a)->CRC; unsigned int CRCB = ((FileInfoStruct*)b)->CRC; if ( CRCA < CRCB ) return -1; if ( CRCA > CRCB ) return 1; return 0; // return ((FileInfoStruct*)a)->CRC - ((FileInfoStruct*)b)->CRC; } MixFileCreator::~MixFileCreator( void ) { if ( MixFile != NULL ) { // Save Header Data int header_offset = MixFile->Tell(); // Save file count int i,num_files = FileInfo.Count(); WWDEBUG_SAY(( "Closing with %d files\n", num_files )); MixFile->Write( &num_files, sizeof( num_files ) ); if ( num_files > 1 ) { qsort( &FileInfo[0], num_files, sizeof(FileInfo[0]), &File_Info_Compare); } // Save file info (CRC, Offset, Size ) for ( i = 0; i < num_files; i++ ) { MixFile->Write( &FileInfo[i].CRC, 4 ); MixFile->Write( &FileInfo[i].Offset, 4 ); MixFile->Write( &FileInfo[i].Size, 4 ); // WWDEBUG_SAY(( "Write CRC %08X\n", FileInfo[i].CRC )); } // --------------------------------------- // Save Names Data int names_offset = MixFile->Tell(); // Save file count MixFile->Write( &num_files, sizeof( num_files ) ); // Save file info for ( i = 0; i < num_files; i++ ) { const char * filename = FileInfo[i].Filename; int size = FileInfo[i].Filename.Get_Length()+1; WWASSERT( size < 255 ); unsigned char csize = size; MixFile->Write( &csize, 1 ); MixFile->Write( filename, size ); } // --------------------------------------- MixFile->Seek( 4, SEEK_SET ); // Save header offset WWDEBUG_SAY(( "Writing header offset %d (%08X)\n", header_offset, header_offset )); MixFile->Write( &header_offset, sizeof( header_offset ) ); // Save names offset WWDEBUG_SAY(( "Writing names offset %d (%08X)\n", names_offset, names_offset )); MixFile->Write( &names_offset, sizeof( names_offset ) ); // --------------------------------------- MixFile->Close(); _SimpleFileFactory.Return_File(MixFile); } } void MixFileCreator::Add_File( const char * source_filename, const char * saved_filename ) { if ( saved_filename == NULL ) { saved_filename = source_filename; } if ( MixFile != NULL ) { FileClass * file = _SimpleFileFactory.Get_File( source_filename ); if ( file && file->Is_Available() ) { file->Open(); MixFileCreator::FileInfoStruct info; info.CRC = CRC_Stringi( saved_filename ); info.Offset = MixFile->Tell(); info.Size = file->Size(); FileInfo.Add( info ); FileInfo[ FileInfo.Count()-1 ].Filename = saved_filename; WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n", saved_filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size )); int size = file->Size(); while ( size ) { char buffer[ 4096 ]; int amount = MIN( sizeof( buffer ), size ); size -= amount; file->Read( buffer, amount ); if ( MixFile->Write( buffer, amount ) != amount ) { WWDEBUG_SAY(( "Failed to write MixFile\n" )); } } // Pad the MixFile to make DWord Aligned int offset = MixFile->Tell(); offset = (8-(offset & 7)) & 7; if ( offset != 0 ) { char zeros[8] = {0,0,0,0,0,0,0,0}; if ( MixFile->Write( zeros, offset ) != offset ) { WWDEBUG_SAY(( "Failed to write padding\n" )); } } file->Close(); _SimpleFileFactory.Return_File( file ); } else { WWDEBUG_SAY(( "MixFileCreator::Failed to open \"%s\"\n", source_filename )); } } } void MixFileCreator::Add_File( const char * filename, FileClass *file ) { if ( MixFile != NULL ) { MixFileCreator::FileInfoStruct info; info.CRC = CRC_Stringi( filename ); info.Offset = MixFile->Tell(); info.Size = file->Size(); FileInfo.Add( info ); FileInfo[ FileInfo.Count()-1 ].Filename = filename; WWDEBUG_SAY(( "Saving File %s CRC %08X Offset %d (0x%08X) Size %d (0x%08X)\n", filename, info.CRC, info.Offset, info.Offset, info.Size, info.Size )); int size = file->Size(); while ( size ) { char buffer[ 4096 ]; int amount = MIN( sizeof( buffer ), size ); size -= amount; file->Read( buffer, amount ); if ( MixFile->Write( buffer, amount ) != amount ) { WWDEBUG_SAY(( "Failed to write MixFile\n" )); } } // Pad the MixFile to make DWord Aligned int offset = MixFile->Tell(); offset = (8-(offset & 7)) & 7; if ( offset != 0 ) { char zeros[8] = {0,0,0,0,0,0,0,0}; if ( MixFile->Write( zeros, offset ) != offset ) { WWDEBUG_SAY(( "Failed to write padding\n" )); } } } return ; } /* ** */ void Add_Files( const char * dir, MixFileCreator & mix ) { BOOL bcontinue = TRUE; HANDLE hfile_find; WIN32_FIND_DATA find_info = {0}; StringClass path; path.Format( "data\\makemix\\%s*.*", dir ); WWDEBUG_SAY(( "Adding files from %s\n", path )); for (hfile_find = ::FindFirstFile( path, &find_info); (hfile_find != INVALID_HANDLE_VALUE) && bcontinue; bcontinue = ::FindNextFile(hfile_find, &find_info)) { if ( find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { if ( find_info.cFileName[0] != '.' ) { StringClass path; path.Format( "%s%s\\", dir, find_info.cFileName ); Add_Files( path, mix ); } } else { StringClass name; name.Format( "%s%s", dir, find_info.cFileName ); StringClass source; source.Format( "makemix\\%s", name ); mix.Add_File( source, name ); // WWDEBUG_SAY(( "Adding file from %s %s\n", source, name )); } } } void Setup_Mix_File( void ) { _SimpleFileFactory.Set_Sub_Directory( "DATA\\" ); // _SimpleFileFactory.Set_Strip_Path( true ); WWDEBUG_SAY(( "Mix File Create .....\n" )); { MixFileCreator mix( "MAKEMIX.MIX" ); Add_Files( "", mix ); } }