/* ** 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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : leveledit * * * * $Archive:: /Commando/Code/Tools/LevelEdit/mixfiledatabase.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 6/19/02 3:58p $* * * * $Revision:: 5 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "mixfiledatabase.h" #include "registry.h" #include "utils.h" #include "mixfile.h" #include "rawfile.h" #include "shlwapi.h" ////////////////////////////////////////////////////////////////////// // Static member initialization ////////////////////////////////////////////////////////////////////// MixFileDatabaseClass *MixFileDatabaseClass::_TheInstance = NULL; ////////////////////////////////////////////////////////////////////// // // MixFileDatabaseClass // ////////////////////////////////////////////////////////////////////// MixFileDatabaseClass::MixFileDatabaseClass (void) { // // Open Renegade's registry // const char * const RENEGADE_REG_KEY = "Software\\Westwood\\Renegade"; RegistryClass registry (RENEGADE_REG_KEY); if (registry.Is_Valid ()) { // // Read the installation path from the registry // StringClass install_path; const char * const RENEGADE_INSTALL_VALUE = "InstallPath"; registry.Get_String (RENEGADE_INSTALL_VALUE, install_path); if (install_path.Get_Length () > 0) { // // The mix files are contained in the data sub-directory // install_path = ::Strip_Filename_From_Path (install_path); MixFilePath = ::Make_Path (install_path, "DATA"); } } _TheInstance = this; return ; } ////////////////////////////////////////////////////////////////////// // // ~MixFileDatabaseClass // ////////////////////////////////////////////////////////////////////// MixFileDatabaseClass::~MixFileDatabaseClass (void) { _TheInstance = NULL; return ; } ////////////////////////////////////////////////////////////////////// // // Open_Database // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Open_Database ( LPCTSTR ini_filename, LPCTSTR username, LPCTSTR password ) { // // Add the always mix files to the file factory list // CString always_dbs_path = ::Make_Path (MixFilePath, "Always.dbs"); CString always_dat_path = ::Make_Path (MixFilePath, "Always.dat"); MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dbs_path, &RenegadeDataFileFactory), "always.dat"); MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dat_path, &RenegadeDataFileFactory), "always.dbs"); // // Build a search path for mix files in the data directory // CString search_path = ::Make_Path (MixFilePath, "*.mix"); // // Search for all mix files in the installation directory // WIN32_FIND_DATA find_info = { 0 }; BOOL keep_going = TRUE; HANDLE file_find = NULL; for (file_find = ::FindFirstFile (search_path, &find_info); (file_find != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (file_find, &find_info)) { // // Add this mix file to our mix file factory list // CString full_path = ::Make_Path (MixFilePath, find_info.cFileName); MainFileFactory.Add_FileFactory (new MixFileFactoryClass (full_path, &RenegadeDataFileFactory), find_info.cFileName); } // // Close the search handle // if (file_find != INVALID_HANDLE_VALUE) { ::FindClose (file_find); } return true; } ////////////////////////////////////////////////////////////////////// // // Check_Out // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Check_Out (LPCTSTR local_filename, bool get_locally) { bool retval = true; // // Simply do a get as necessary // if (get_locally) { retval = Get (local_filename); } return retval; } ////////////////////////////////////////////////////////////////////// // // Get // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Get (LPCTSTR local_filename) { StringClass filename; Get_Filename (local_filename, filename); StringClass full_local_path = local_filename; // // Check to see if we need to swap texture extensions // if (Internal_Does_File_Exist (filename) == false && Is_Texture (filename)) { Swap_Texture_Extension (filename); StringClass local_path = (const char *)::Strip_Filename_From_Path (local_filename); full_local_path = ::Make_Path (local_path, filename); } // // Get the file // return Internal_Get (filename, full_local_path); } ////////////////////////////////////////////////////////////////////// // // Internal_Get // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Internal_Get (LPCTSTR filename, LPCSTR local_path) { bool retval = false; // // Get the file from one of the mix files // FileClass *file = MainFileFactory.Get_File (filename); if (file != NULL) { // // Copy the file if it exists // if (file->Is_Available () && file->Open ()) { retval = Copy_File (file, local_path); file->Close (); } MainFileFactory.Return_File (file); } return retval; } ////////////////////////////////////////////////////////////////////// // // Copy_File // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Copy_File (FileClass *src_file, LPCTSTR local_filename) { if ( src_file == NULL || local_filename == NULL || ::GetFileAttributes (local_filename) != 0xFFFFFFFF) { return false; } bool retval = false; // // Ensure the directory structure exists before we attempt to create the file // StringClass path = (const char *)::Strip_Filename_From_Path (local_filename); Create_Directory_Structure (path); // // Create the destination file // RawFileClass dest_file; dest_file.Set_Name (local_filename); if (dest_file.Open (RawFileClass::WRITE)) { retval = true; // // Copy the data from the source file to the destination file // int file_size = src_file->Size (); uint8 buffer[4096]; while (file_size > 0) { // // Read the data from the source file // int bytes = min (file_size, (int)sizeof (buffer)); int copied_size = src_file->Read (buffer, bytes); file_size -= copied_size; if (copied_size <= 0) { break; } // // Copy the data to the dest file (kick out of the loop on error) // if (dest_file.Write (buffer, copied_size) != copied_size) { break; } } // // Close the destination file // dest_file.Close (); } return retval; } ////////////////////////////////////////////////////////////////////// // // Does_File_Exist // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Does_File_Exist (LPCTSTR local_filename) { bool retval = false; // // Test to see if the file exists // StringClass filename; Get_Filename (local_filename, filename); retval = Internal_Does_File_Exist (filename); // // If the file did not exists, check to see if its a texture... // if (retval == false && Is_Texture (filename)) { // // Check to see if either the compressed or uncompressed // texture exists // Swap_Texture_Extension (filename); retval = Internal_Does_File_Exist (filename); } return retval; } ////////////////////////////////////////////////////////////////////// // // Internal_Does_File_Exist // ////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Internal_Does_File_Exist (LPCSTR filename) { bool retval = false; // // Get the file from one of the mix files // FileClass *file = MainFileFactory.Get_File (filename); if (file != NULL) { // // Does the file exist? // if (file->Is_Available ()) { retval = true; } MainFileFactory.Return_File (file); } return retval; } ////////////////////////////////////////////////////////////////////////////////// // // Create_Directory_Structure // ////////////////////////////////////////////////////////////////////////////////// void MixFileDatabaseClass::Create_Directory_Structure (LPCTSTR path) { if (path != NULL && path[0] != 0 && ::GetFileAttributes (path) == 0xFFFFFFFF) { Create_Directory_Structure (::Strip_Filename_From_Path (path)); ::CreateDirectory (path, NULL); } return ; } ////////////////////////////////////////////////////////////////////////////////// // // Is_Texture // ////////////////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Is_Texture (LPCSTR filename) { StringClass temp_str (filename, true); ::strlwr (temp_str.Peek_Buffer ()); // // Check to see if this is either a compressed or uncompressed texture // bool retval = false; if (::strstr (temp_str.Peek_Buffer (), ".tga") || ::strstr (temp_str.Peek_Buffer (), ".dds")) { retval = true; } return retval; } ////////////////////////////////////////////////////////////////////////////////// // // Swap_Texture_Extension // ////////////////////////////////////////////////////////////////////////////////// void MixFileDatabaseClass::Swap_Texture_Extension (StringClass &filename) { ::strlwr (filename.Peek_Buffer ()); // // Is this a tga file (uncompressed), or a dds file (compressed)? // char *tga_extension = ::strstr (filename.Peek_Buffer (), ".tga"); if (tga_extension != NULL) { // // Simply copy the new extension into the string // ::strcpy (tga_extension, ".dds"); } else { char *dds_extension = ::strstr (filename.Peek_Buffer (), ".dds"); if (dds_extension != NULL) { // // Simply copy the new extension into the string // ::strcpy (dds_extension, ".tga"); } } return ; } ////////////////////////////////////////////////////////////////////////////////// // // Find_Files // ////////////////////////////////////////////////////////////////////////////////// void MixFileDatabaseClass::Find_Files (DynamicVectorClass &file_list, LPCTSTR search_mask) { // // Loop over all the factories // int factory_count = MainFileFactory.Get_Factory_Count (); for (int index = 0; index < factory_count; index ++) { // // Get a pointer to the current factory // FileFactoryClass *factory = MainFileFactory.Get_Factory (index); if (factory != NULL) { // // We assume that this is a mix file factory (since all the factories // we're adding are mix file factories). Note: This can easily // break. // MixFileFactoryClass *mix_factory = static_cast (factory); // // Get a list of all the files in this mix file // DynamicVectorClass files_in_mix; files_in_mix.Set_Growth_Step (1000); mix_factory->Build_Filename_List (files_in_mix); // // Now, add any files that match the supplied wildcard to our master list // for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) { const char *filename = files_in_mix[file_index]; // // Add this file to our list if it matches the mask // if (::PathMatchSpec (filename, search_mask)) { file_list.Add (filename); } } } } return ; } ////////////////////////////////////////////////////////////////////////////////// // // Get_All // ////////////////////////////////////////////////////////////////////////////////// bool MixFileDatabaseClass::Get_All (LPCTSTR dest_path, LPCTSTR search_mask) { DynamicVectorClass file_list; // // Loop over all the factories // int factory_count = MainFileFactory.Get_Factory_Count (); for (int index = 0; index < factory_count; index ++) { // // Get a pointer to the current factory // FileFactoryClass *factory = MainFileFactory.Get_Factory (index); if (factory != NULL) { // // We assume that this is a mix file factory (since all the factories // we're adding are mix file factories). Note: This can easily // break. // MixFileFactoryClass *mix_factory = static_cast (factory); // // Get a list of all the files in this mix file // DynamicVectorClass files_in_mix; files_in_mix.Set_Growth_Step (1000); mix_factory->Build_Filename_List (files_in_mix); // // Now, add any files that match the supplied wildcard to our master list // for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) { const char *filename = files_in_mix[file_index]; // // Add this file to our list if it matches the mask // if (::PathMatchSpec (filename, search_mask)) { file_list.Add (filename); } } } } // // Now, get all the matching files to the specified path // for (index = 0; index < file_list.Count (); index ++) { StringClass full_path = (const char *)::Make_Path (dest_path, file_list[index]); Get (full_path); } return true; } /////////////////////////////////////////////////////////////////// // // Get_File // /////////////////////////////////////////////////////////////////// FileClass * MixFileDatabaseClass::Get_File (LPCTSTR local_filename) { FileClass *retval = NULL; if (::GetFileAttributes (local_filename) != 0xFFFFFFFF) { // // Do this to get around an oddity of the class when you pass in // the filename to the constructor // retval = new RawFileClass; retval->Set_Name (local_filename); } else { // // Get a pointer to the file in the mix file // StringClass filename; Get_Filename (local_filename, filename); retval = MainFileFactory.Get_File (filename); } return retval; } /////////////////////////////////////////////////////////////////// // // Get_Filename // /////////////////////////////////////////////////////////////////// void MixFileDatabaseClass::Get_Filename (LPCTSTR path, StringClass &filename) { filename = path; ::strlwr (filename.Peek_Buffer ()); // // Check to see if the sub-directory is important, if it is, then // return the sub-directory as well // char *subdir_token = ::strstr (filename, "+\\"); if (subdir_token != NULL) { // // Try to find the preceeding directory delimiter // int index = (subdir_token - filename.Peek_Buffer ()); for (; index >= 0; index --) { if (filename[index] == '\\') { StringClass temp_str = filename; filename = (temp_str.Peek_Buffer () + index + 1); break; } } } else { // // Simply strip the filename from the path // filename = (const char *)::Get_Filename_From_Path (filename); } return ; }