/* ** 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/editormixfile.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 9/12/01 12:38p $* * * * $Revision:: 7 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "filemgr.h" #include "editormixfile.h" #include "utils.h" #include "mixfile.h" #include "TGAToDXT.h" #include "LevelEdit.h" #include "RegKeys.h" #include #include ///////////////////////////////////////////////////////////////////////// // // EditorMixFileCreator // ///////////////////////////////////////////////////////////////////////// EditorMixFileCreator::EditorMixFileCreator() { // OPTIMIZATION: Store state of texture compression for quick access. TexturesCompressed = Are_Textures_Compressed(); } ///////////////////////////////////////////////////////////////////////// // // Add_File // ///////////////////////////////////////////////////////////////////////// void EditorMixFileCreator::Add_File (const char *full_path, const char *name) { char substitutefullpath [MAX_PATH]; char substitutename [MAX_PATH]; // See if another file needs to be substituted for the input file. Substitute_File (full_path, name, substitutefullpath, substitutename); StringClass lower_name = substitutename; ::strlwr (lower_name.Peek_Buffer ()); // // Is this file already in the system? // EditorMixFileEntry *stored_path = FilenameHash.Get (lower_name); if (stored_path != NULL) { // // Don't store the new file unless its newer then the one already // in the hash // if (::Quick_Compare_Files (stored_path->Get_Path (), substitutefullpath) > 0) { // // Remap this entry to the new path // stored_path->Set_Path (substitutefullpath); } } else { // // Allocate a new entry... // stored_path = new EditorMixFileEntry; stored_path->Set_Name (lower_name); stored_path->Set_Path (substitutefullpath); // // Now add this entry to the hash // FilenameHash.Insert (lower_name, stored_path); } return ; } ///////////////////////////////////////////////////////////////////////// // // Substitute_File // ///////////////////////////////////////////////////////////////////////// void EditorMixFileCreator::Substitute_File (const char *fullpath, const char *name, char *substitutefullpath, char *substitutename) { bool substitutefile; char filename [_MAX_FNAME]; char extension [_MAX_EXT]; CString cachedfullpath; CString cachedname; // Assume that another file will not be substituted for the original file. substitutefile = false; // Has compression been enabled by user? if (TexturesCompressed) { // Is this file a .TGA? If so use a compressed (.DDS) version stored in the texture cache. _splitpath (fullpath, NULL, NULL, filename, extension); if (_stricmp (extension, ".tga") == 0) { const char *exclusionstring0 = "bump_"; const char *exclusionstring1 = "font"; // Does this filename not start with one of the exclusion strings? // NOTE 0: bump_*.* files are bump maps and cannot be compressed because the pixel data is interpreted as bump data. // NOTE 1: font*.* files are font files that are not processed by the run-time compressed texture loading system and, therefore, must be excluded. if ((_strnicmp (filename, exclusionstring0, strlen (exclusionstring0)) != 0) && (_strnicmp (filename, exclusionstring1, strlen (exclusionstring1)) != 0)) { const char *ddsextension = ".dds"; const char *failedtoopentext = "Failed to open %s\r\n"; char mangleddirectory [_MAX_DIR]; char mangledname [_MAX_FNAME]; HANDLE tgafile; HANDLE ddsfile; char *s; // OPTIMIZATION: Has the .DDS file already been created and stored in the texture cache? // NOTE: If the name contains a subdirectory mangle it with the filename. This will ensure that the filename is unique. _splitpath (name, NULL, mangleddirectory, mangledname, NULL); // NOTE: The cached name must be the same as the original name but with .DDS extension. cachedname = mangleddirectory; cachedname += mangledname; cachedname += ddsextension; // Replace backslashes in the mangled directory with dots to 'flatten' it. s = mangleddirectory; while (*s != '\0') { if ((*s == '\\') || (*s == '/')) { *s = '.'; } s++; } cachedfullpath = ::Get_File_Mgr()->Get_Texture_Cache_Path(); cachedfullpath += "\\"; cachedfullpath += mangleddirectory; cachedfullpath += filename; cachedfullpath += ddsextension; // Does the cached .DDS file exist? tgafile = CreateFile (fullpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0L, NULL); ddsfile = CreateFile (cachedfullpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0L, NULL); if (tgafile != INVALID_HANDLE_VALUE) { FILETIME tgawritetime; GetFileTime (tgafile, NULL, NULL, &tgawritetime); if (ddsfile != INVALID_HANDLE_VALUE) { FILETIME ddswritetime; // Does the cached .DDS file have the same time stamp as the .TGA file? GetFileTime (ddsfile, NULL, NULL, &ddswritetime); CloseHandle (tgafile); CloseHandle (ddsfile); if ((tgawritetime.dwLowDateTime == ddswritetime.dwLowDateTime) && (tgawritetime.dwHighDateTime == ddswritetime.dwHighDateTime)) { // Files have the same time stamp. Substitute the .DDS file. substitutefile = true; } else { // Files have a different time stamp (and therefore it is assumed that the .TGA file has been modified since the .DDS file was created). // Create a new .DDS file from the .TGA file and give it the same time stamp as the .TGA file. substitutefile = Convert_File (fullpath, cachedfullpath, &tgawritetime); } } else { CloseHandle (tgafile); // Create a new .DDS file from the .TGA file and give it the same time stamp as the .TGA file. substitutefile = Convert_File (fullpath, cachedfullpath, &tgawritetime); } } else { CString message; // Cannot open the input file! message.Format ("Failed to open: %s\r\n", fullpath); ::Output_Message (message); } } } } if (substitutefile) { // Substitute the cached file. strcpy (substitutefullpath, cachedfullpath); strcpy (substitutename, cachedname); } else { // Refer the caller back to the original input file ie. no file is substituted. strcpy (substitutefullpath, fullpath); strcpy (substitutename, name); } } ///////////////////////////////////////////////////////////////////////// // // Generate_Mix_File // ///////////////////////////////////////////////////////////////////////// bool EditorMixFileCreator::Convert_File (const char *fullpath, const char *cachedfullpath, FILETIME *tgawritetimeptr) { const char *failedtocompresstext = "Failed to compress %s\r\n"; const char *removedalphatext = "Removed alpha channel in %s\r\n"; bool success, alpharemoved; CString message; success = _TGAToDXTConverter.Convert (fullpath, cachedfullpath, tgawritetimeptr, alpharemoved); if (!success) { message.Format (failedtocompresstext, fullpath); ::Output_Message (message); } if (alpharemoved) { message.Format (removedalphatext, fullpath); ::Output_Message (message); } return (success); } ///////////////////////////////////////////////////////////////////////// // // Generate_Mix_File // ///////////////////////////////////////////////////////////////////////// void EditorMixFileCreator::Generate_Mix_File (const char *full_path) { MixFileCreator mix_file (full_path); // // Loop over all the entries in hash table // HashTemplateIterator iterator (FilenameHash); for (iterator.First (); iterator.Is_Done () == false; iterator.Next ()) { EditorMixFileEntry *entry = iterator.Peek_Value (); // // Add this file to the mix // mix_file.Add_File (entry->Get_Path (), entry->Get_Name ()); } return ; } ///////////////////////////////////////////////////////////////////////// // // Flush // ///////////////////////////////////////////////////////////////////////// void EditorMixFileCreator::Flush (void) { // // Loop over all the entries in hash table // HashTemplateIterator iterator (FilenameHash); for (iterator.First (); iterator.Is_Done () == false; iterator.Next ()) { // // Free this entry // EditorMixFileEntry *entry = iterator.Peek_Value (); delete entry; } FilenameHash.Remove_All (); return ; } ///////////////////////////////////////////////////////////////////////// // // Set_Texture_Compression // ///////////////////////////////////////////////////////////////////////// void EditorMixFileCreator::Set_Texture_Compression (bool onoff) { // Update the registry. theApp.WriteProfileInt (CONFIG_KEY, TEXTURE_COMPRESSION_VALUE, onoff); } ///////////////////////////////////////////////////////////////////////// // // Are_Textures_Compressed // ///////////////////////////////////////////////////////////////////////// bool EditorMixFileCreator::Are_Textures_Compressed() { // Read from the registry. return (theApp.GetProfileInt (CONFIG_KEY, TEXTURE_COMPRESSION_VALUE, 0) == 1); }