editormixfile.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /*
  2. ** Command & Conquer Renegade(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. *** 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 ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : LevelEdit *
  23. * *
  24. * $Archive:: /Commando/Code/Tools/LevelEdit/editormixfile.cpp $*
  25. * *
  26. * Author:: Patrick Smith *
  27. * *
  28. * $Modtime:: 9/12/01 12:38p $*
  29. * *
  30. * $Revision:: 7 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "stdafx.h"
  36. #include "filemgr.h"
  37. #include "editormixfile.h"
  38. #include "utils.h"
  39. #include "mixfile.h"
  40. #include "TGAToDXT.h"
  41. #include "LevelEdit.h"
  42. #include "RegKeys.h"
  43. #include <stdlib.h>
  44. #include <winbase.h>
  45. /////////////////////////////////////////////////////////////////////////
  46. //
  47. // EditorMixFileCreator
  48. //
  49. /////////////////////////////////////////////////////////////////////////
  50. EditorMixFileCreator::EditorMixFileCreator()
  51. {
  52. // OPTIMIZATION: Store state of texture compression for quick access.
  53. TexturesCompressed = Are_Textures_Compressed();
  54. }
  55. /////////////////////////////////////////////////////////////////////////
  56. //
  57. // Add_File
  58. //
  59. /////////////////////////////////////////////////////////////////////////
  60. void
  61. EditorMixFileCreator::Add_File (const char *full_path, const char *name)
  62. {
  63. char substitutefullpath [MAX_PATH];
  64. char substitutename [MAX_PATH];
  65. // See if another file needs to be substituted for the input file.
  66. Substitute_File (full_path, name, substitutefullpath, substitutename);
  67. StringClass lower_name = substitutename;
  68. ::strlwr (lower_name.Peek_Buffer ());
  69. //
  70. // Is this file already in the system?
  71. //
  72. EditorMixFileEntry *stored_path = FilenameHash.Get (lower_name);
  73. if (stored_path != NULL) {
  74. //
  75. // Don't store the new file unless its newer then the one already
  76. // in the hash
  77. //
  78. if (::Quick_Compare_Files (stored_path->Get_Path (), substitutefullpath) > 0) {
  79. //
  80. // Remap this entry to the new path
  81. //
  82. stored_path->Set_Path (substitutefullpath);
  83. }
  84. } else {
  85. //
  86. // Allocate a new entry...
  87. //
  88. stored_path = new EditorMixFileEntry;
  89. stored_path->Set_Name (lower_name);
  90. stored_path->Set_Path (substitutefullpath);
  91. //
  92. // Now add this entry to the hash
  93. //
  94. FilenameHash.Insert (lower_name, stored_path);
  95. }
  96. return ;
  97. }
  98. /////////////////////////////////////////////////////////////////////////
  99. //
  100. // Substitute_File
  101. //
  102. /////////////////////////////////////////////////////////////////////////
  103. void EditorMixFileCreator::Substitute_File (const char *fullpath, const char *name, char *substitutefullpath, char *substitutename)
  104. {
  105. bool substitutefile;
  106. char filename [_MAX_FNAME];
  107. char extension [_MAX_EXT];
  108. CString cachedfullpath;
  109. CString cachedname;
  110. // Assume that another file will not be substituted for the original file.
  111. substitutefile = false;
  112. // Has compression been enabled by user?
  113. if (TexturesCompressed) {
  114. // Is this file a .TGA? If so use a compressed (.DDS) version stored in the texture cache.
  115. _splitpath (fullpath, NULL, NULL, filename, extension);
  116. if (_stricmp (extension, ".tga") == 0) {
  117. const char *exclusionstring0 = "bump_";
  118. const char *exclusionstring1 = "font";
  119. // Does this filename not start with one of the exclusion strings?
  120. // NOTE 0: bump_*.* files are bump maps and cannot be compressed because the pixel data is interpreted as bump data.
  121. // NOTE 1: font*.* files are font files that are not processed by the run-time compressed texture loading system and, therefore, must be excluded.
  122. if ((_strnicmp (filename, exclusionstring0, strlen (exclusionstring0)) != 0) &&
  123. (_strnicmp (filename, exclusionstring1, strlen (exclusionstring1)) != 0)) {
  124. const char *ddsextension = ".dds";
  125. const char *failedtoopentext = "Failed to open %s\r\n";
  126. char mangleddirectory [_MAX_DIR];
  127. char mangledname [_MAX_FNAME];
  128. HANDLE tgafile;
  129. HANDLE ddsfile;
  130. char *s;
  131. // OPTIMIZATION: Has the .DDS file already been created and stored in the texture cache?
  132. // NOTE: If the name contains a subdirectory mangle it with the filename. This will ensure that the filename is unique.
  133. _splitpath (name, NULL, mangleddirectory, mangledname, NULL);
  134. // NOTE: The cached name must be the same as the original name but with .DDS extension.
  135. cachedname = mangleddirectory;
  136. cachedname += mangledname;
  137. cachedname += ddsextension;
  138. // Replace backslashes in the mangled directory with dots to 'flatten' it.
  139. s = mangleddirectory;
  140. while (*s != '\0') {
  141. if ((*s == '\\') || (*s == '/')) {
  142. *s = '.';
  143. }
  144. s++;
  145. }
  146. cachedfullpath = ::Get_File_Mgr()->Get_Texture_Cache_Path();
  147. cachedfullpath += "\\";
  148. cachedfullpath += mangleddirectory;
  149. cachedfullpath += filename;
  150. cachedfullpath += ddsextension;
  151. // Does the cached .DDS file exist?
  152. tgafile = CreateFile (fullpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0L, NULL);
  153. ddsfile = CreateFile (cachedfullpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0L, NULL);
  154. if (tgafile != INVALID_HANDLE_VALUE) {
  155. FILETIME tgawritetime;
  156. GetFileTime (tgafile, NULL, NULL, &tgawritetime);
  157. if (ddsfile != INVALID_HANDLE_VALUE) {
  158. FILETIME ddswritetime;
  159. // Does the cached .DDS file have the same time stamp as the .TGA file?
  160. GetFileTime (ddsfile, NULL, NULL, &ddswritetime);
  161. CloseHandle (tgafile);
  162. CloseHandle (ddsfile);
  163. if ((tgawritetime.dwLowDateTime == ddswritetime.dwLowDateTime) && (tgawritetime.dwHighDateTime == ddswritetime.dwHighDateTime)) {
  164. // Files have the same time stamp. Substitute the .DDS file.
  165. substitutefile = true;
  166. } else {
  167. // Files have a different time stamp (and therefore it is assumed that the .TGA file has been modified since the .DDS file was created).
  168. // Create a new .DDS file from the .TGA file and give it the same time stamp as the .TGA file.
  169. substitutefile = Convert_File (fullpath, cachedfullpath, &tgawritetime);
  170. }
  171. } else {
  172. CloseHandle (tgafile);
  173. // Create a new .DDS file from the .TGA file and give it the same time stamp as the .TGA file.
  174. substitutefile = Convert_File (fullpath, cachedfullpath, &tgawritetime);
  175. }
  176. } else {
  177. CString message;
  178. // Cannot open the input file!
  179. message.Format ("Failed to open: %s\r\n", fullpath);
  180. ::Output_Message (message);
  181. }
  182. }
  183. }
  184. }
  185. if (substitutefile) {
  186. // Substitute the cached file.
  187. strcpy (substitutefullpath, cachedfullpath);
  188. strcpy (substitutename, cachedname);
  189. } else {
  190. // Refer the caller back to the original input file ie. no file is substituted.
  191. strcpy (substitutefullpath, fullpath);
  192. strcpy (substitutename, name);
  193. }
  194. }
  195. /////////////////////////////////////////////////////////////////////////
  196. //
  197. // Generate_Mix_File
  198. //
  199. /////////////////////////////////////////////////////////////////////////
  200. bool EditorMixFileCreator::Convert_File (const char *fullpath, const char *cachedfullpath, FILETIME *tgawritetimeptr)
  201. {
  202. const char *failedtocompresstext = "Failed to compress %s\r\n";
  203. const char *removedalphatext = "Removed alpha channel in %s\r\n";
  204. bool success, alpharemoved;
  205. CString message;
  206. success = _TGAToDXTConverter.Convert (fullpath, cachedfullpath, tgawritetimeptr, alpharemoved);
  207. if (!success) {
  208. message.Format (failedtocompresstext, fullpath);
  209. ::Output_Message (message);
  210. }
  211. if (alpharemoved) {
  212. message.Format (removedalphatext, fullpath);
  213. ::Output_Message (message);
  214. }
  215. return (success);
  216. }
  217. /////////////////////////////////////////////////////////////////////////
  218. //
  219. // Generate_Mix_File
  220. //
  221. /////////////////////////////////////////////////////////////////////////
  222. void
  223. EditorMixFileCreator::Generate_Mix_File (const char *full_path)
  224. {
  225. MixFileCreator mix_file (full_path);
  226. //
  227. // Loop over all the entries in hash table
  228. //
  229. HashTemplateIterator<StringClass, EditorMixFileEntry *> iterator (FilenameHash);
  230. for (iterator.First (); iterator.Is_Done () == false; iterator.Next ()) {
  231. EditorMixFileEntry *entry = iterator.Peek_Value ();
  232. //
  233. // Add this file to the mix
  234. //
  235. mix_file.Add_File (entry->Get_Path (), entry->Get_Name ());
  236. }
  237. return ;
  238. }
  239. /////////////////////////////////////////////////////////////////////////
  240. //
  241. // Flush
  242. //
  243. /////////////////////////////////////////////////////////////////////////
  244. void
  245. EditorMixFileCreator::Flush (void)
  246. {
  247. //
  248. // Loop over all the entries in hash table
  249. //
  250. HashTemplateIterator<StringClass, EditorMixFileEntry *> iterator (FilenameHash);
  251. for (iterator.First (); iterator.Is_Done () == false; iterator.Next ()) {
  252. //
  253. // Free this entry
  254. //
  255. EditorMixFileEntry *entry = iterator.Peek_Value ();
  256. delete entry;
  257. }
  258. FilenameHash.Remove_All ();
  259. return ;
  260. }
  261. /////////////////////////////////////////////////////////////////////////
  262. //
  263. // Set_Texture_Compression
  264. //
  265. /////////////////////////////////////////////////////////////////////////
  266. void EditorMixFileCreator::Set_Texture_Compression (bool onoff)
  267. {
  268. // Update the registry.
  269. theApp.WriteProfileInt (CONFIG_KEY, TEXTURE_COMPRESSION_VALUE, onoff);
  270. }
  271. /////////////////////////////////////////////////////////////////////////
  272. //
  273. // Are_Textures_Compressed
  274. //
  275. /////////////////////////////////////////////////////////////////////////
  276. bool EditorMixFileCreator::Are_Textures_Compressed()
  277. {
  278. // Read from the registry.
  279. return (theApp.GetProfileInt (CONFIG_KEY, TEXTURE_COMPRESSION_VALUE, 0) == 1);
  280. }