/*
** 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);
}