textureCompress.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. /*
  2. ** Command & Conquer Generals(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. // FILE: textureCompress.cpp //////////////////////////////////////////////////////
  19. // Author: Matthew D. Campbell, Dec 2002
  20. // SYSTEM INCLUDES ////////////////////////////////////////////////////////////
  21. #define WIN32_LEAN_AND_MEAN // only bare bones windows stuff wanted
  22. //#include <afxwin.h>
  23. #include <windows.h>
  24. #include <lmcons.h>
  25. #include <stdlib.h>
  26. #include <stdio.h>
  27. #include <string.h>
  28. #include "resource.h"
  29. #include <map>
  30. #include <string>
  31. #include <set>
  32. #include <cstdarg>
  33. #include <io.h>
  34. #include <sys/stat.h>
  35. #include <sys/utime.h>
  36. static const char *nodxtPrefix[] = {
  37. "zhca",
  38. "caust",
  39. NULL,
  40. };
  41. static const char *nodxtAnywhere[] = {
  42. "userinterface",
  43. "controlbar",
  44. "commandbar",
  45. NULL,
  46. };
  47. #define LOG(x) logStuff x
  48. static void logStuff(const char *fmt, ...)
  49. {
  50. static char buffer[1024];
  51. va_list va;
  52. va_start( va, fmt );
  53. _vsnprintf(buffer, 1024, fmt, va );
  54. buffer[1023] = 0;
  55. va_end( va );
  56. puts(buffer);
  57. ::MessageBox(NULL, buffer, "textureCompress", MB_OK);
  58. }
  59. #ifndef NDEBUG
  60. class DebugMunkee
  61. {
  62. public:
  63. DebugMunkee(const char *fname = "debugLog.txt") { m_fp = fopen(fname, "w"); }
  64. ~DebugMunkee() { if (m_fp) fclose(m_fp); m_fp = NULL; }
  65. FILE *m_fp;
  66. };
  67. static DebugMunkee *theDebugMunkee = NULL;
  68. #define DEBUG_LOG(x) debugLog x
  69. static void debugLog(const char *fmt, ...)
  70. {
  71. static char buffer[1024];
  72. va_list va;
  73. va_start( va, fmt );
  74. _vsnprintf(buffer, 1024, fmt, va );
  75. buffer[1023] = 0;
  76. va_end( va );
  77. OutputDebugString( buffer );
  78. puts(buffer);
  79. if (theDebugMunkee)
  80. fputs(buffer, theDebugMunkee->m_fp);
  81. }
  82. #else
  83. #define DEBUG_LOG(x) {}
  84. #endif // NDEBUG
  85. static void usage(const char *progname)
  86. {
  87. if (!progname)
  88. progname = "textureCompress";
  89. LOG (("Usage: %s sourceDir destDir cacheDir outFile dxtOutFile\n", progname));
  90. }
  91. class FileInfo
  92. {
  93. public:
  94. FileInfo() {}
  95. ~FileInfo() {}
  96. void set( const WIN32_FIND_DATA& info );
  97. std::string filename;
  98. time_t creationTime;
  99. time_t accessTime;
  100. time_t modTime;
  101. DWORD attributes;
  102. DWORD filesize; // only care about 32 bits for our purposes
  103. protected:
  104. };
  105. struct FileInfoComparator
  106. {
  107. bool operator()(const FileInfo& a, const FileInfo& b) const
  108. {
  109. return a.filename < b.filename;
  110. }
  111. };
  112. //-------------------------------------------------------------------------------------------------
  113. typedef std::set<FileInfo, FileInfoComparator> FileInfoSet;
  114. //-------------------------------------------------------------------------------------------------
  115. class Directory
  116. {
  117. public:
  118. Directory(const std::string& dirPath);
  119. ~Directory() {}
  120. FileInfoSet* getFiles( void );
  121. FileInfoSet* getSubdirs( void );
  122. protected:
  123. std::string m_dirPath;
  124. FileInfoSet m_files;
  125. FileInfoSet m_subdirs;
  126. };
  127. //-------------------------------------------------------------------------------------------------
  128. static void TimetToFileTime( time_t t, FILETIME& ft )
  129. {
  130. LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
  131. ft.dwLowDateTime = (DWORD) ll;
  132. ft.dwHighDateTime = ll >>32;
  133. }
  134. static time_t FileTimeToTimet( const FILETIME& ft )
  135. {
  136. LONGLONG ll = (ft.dwHighDateTime << 32) + ft.dwLowDateTime - 116444736000000000;
  137. ll /= 10000000;
  138. return (time_t)ll;
  139. }
  140. //-------------------------------------------------------------------------------------------------
  141. void FileInfo::set( const WIN32_FIND_DATA& info )
  142. {
  143. filename = info.cFileName;
  144. for (int i=0; i<filename.size(); ++i)
  145. {
  146. char c[2] = { tolower(info.cFileName[i]), 0 };
  147. filename.replace(i, 1, c, 1);
  148. }
  149. creationTime = FileTimeToTimet(info.ftCreationTime);
  150. accessTime = FileTimeToTimet(info.ftLastAccessTime);
  151. modTime = FileTimeToTimet(info.ftLastWriteTime);
  152. attributes = info.dwFileAttributes;
  153. filesize = info.nFileSizeLow;
  154. struct stat origStat;
  155. stat( filename.c_str(), &origStat);
  156. modTime = origStat.st_mtime; // use stat(), since the LONGLONG code is unpredictable
  157. //DEBUG_LOG(("FileInfo::set(): fname=%s, size=%d, modTime=%d\n", filename.c_str(), filesize, modTime));
  158. }
  159. //-------------------------------------------------------------------------------------------------
  160. Directory::Directory( const std::string& dirPath ) : m_dirPath(dirPath)
  161. {
  162. WIN32_FIND_DATA item; // search item
  163. HANDLE hFile; // handle for search resources
  164. char currDir[ MAX_PATH ];
  165. // sanity
  166. if( !m_dirPath.length() )
  167. {
  168. return;
  169. }
  170. // save the current directory
  171. GetCurrentDirectory( MAX_PATH, currDir );
  172. // switch into the directory provided
  173. if( SetCurrentDirectory( m_dirPath.c_str() ) == 0 )
  174. {
  175. return;
  176. }
  177. // go through each item in the output directory
  178. bool done = false;
  179. hFile = FindFirstFile( "*", &item);
  180. if( hFile == INVALID_HANDLE_VALUE )
  181. {
  182. done = true;
  183. }
  184. FileInfo info;
  185. while (!done)
  186. {
  187. // if this is a subdirectory keep the name around till the end
  188. if( item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
  189. {
  190. if ( strcmp( item.cFileName, "." ) && strcmp( item.cFileName, ".." ) )
  191. {
  192. info.set(item);
  193. m_subdirs.insert( info );
  194. }
  195. }
  196. else
  197. {
  198. info.set(item);
  199. m_files.insert( info );
  200. }
  201. if ( FindNextFile( hFile, &item ) == 0 )
  202. {
  203. done = true;
  204. }
  205. }
  206. // close search
  207. FindClose( hFile );
  208. // restore the working directory to what it was when we started here
  209. SetCurrentDirectory( currDir );
  210. }
  211. FileInfoSet* Directory::getFiles( void )
  212. {
  213. return &m_files;
  214. }
  215. FileInfoSet* Directory::getSubdirs( void )
  216. {
  217. return &m_subdirs;
  218. }
  219. //-------------------------------------------------------------------------------------------------
  220. // strtrim ====================================================================
  221. /** Trim leading and trailing whitespace from a character string (in place). */
  222. //=============================================================================
  223. static char* strtrim(char* buffer)
  224. {
  225. if (buffer != NULL) {
  226. // Strip leading white space from the string.
  227. char * source = buffer;
  228. while ((*source != 0) && ((unsigned char)*source <= 32))
  229. {
  230. source++;
  231. }
  232. if (source != buffer)
  233. {
  234. strcpy(buffer, source);
  235. }
  236. // Clip trailing white space from the string.
  237. for (int index = strlen(buffer)-1; index >= 0; index--)
  238. {
  239. if ((*source != 0) && ((unsigned char)buffer[index] <= 32))
  240. {
  241. buffer[index] = '\0';
  242. }
  243. else
  244. {
  245. break;
  246. }
  247. }
  248. }
  249. return buffer;
  250. }
  251. //-------------------------------------------------------------------------------------------------
  252. typedef std::set<std::string> StringSet;
  253. //-------------------------------------------------------------------------------------------------
  254. void eraseCachedFiles(const std::string& sourceDirName, const std::string& targetDirName, const std::string& cacheDirName,
  255. StringSet& cachedFilesToErase)
  256. {
  257. StringSet::const_iterator sit;
  258. for (sit = cachedFilesToErase.begin(); sit != cachedFilesToErase.end(); ++sit)
  259. {
  260. std::string src = cacheDirName;
  261. src.append("\\");
  262. src.append(*sit);
  263. DEBUG_LOG(("Erasing cached file: %s\n", src.c_str()));
  264. DeleteFile(src.c_str());
  265. }
  266. }
  267. //-------------------------------------------------------------------------------------------------
  268. void copyCachedFiles(const std::string& sourceDirName, const std::string& targetDirName, const std::string& cacheDirName,
  269. StringSet& cachedFilesToCopy)
  270. {
  271. StringSet::const_iterator sit;
  272. for (sit = cachedFilesToCopy.begin(); sit != cachedFilesToCopy.end(); ++sit)
  273. {
  274. std::string src = cacheDirName;
  275. src.append("\\");
  276. src.append(*sit);
  277. std::string dest = targetDirName;
  278. dest.append("\\");
  279. dest.append(*sit);
  280. DEBUG_LOG(("Copying cached file: %s\n", src.c_str()));
  281. if (_chmod(dest.c_str(), _S_IWRITE | _S_IREAD) == -1)
  282. {
  283. DEBUG_LOG(("Cannot chmod '%s'\n", dest.c_str()));
  284. }
  285. CopyFile(src.c_str(), dest.c_str(), FALSE);
  286. }
  287. }
  288. //-------------------------------------------------------------------------------------------------
  289. void compressOrigFiles(const std::string& sourceDirName, const std::string& targetDirName, const std::string& cacheDirName,
  290. StringSet& origFilesToCompress, const std::string& dxtOutFname)
  291. {
  292. char tmpPath[_MAX_PATH] = "C:\\temp\\";
  293. char tmpFname[_MAX_PATH] = "C:\\temp\\tmp.txt";
  294. GetTempPath(_MAX_PATH, tmpPath);
  295. GetTempFileName(tmpPath, "tex", 0, tmpFname);
  296. HANDLE h = CreateFile(tmpFname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
  297. if (!h)
  298. {
  299. DEBUG_LOG(("Could not create temp file '%s'! Unable to compress textures!\n", tmpFname));
  300. }
  301. StringSet::const_iterator sit;
  302. for (sit = origFilesToCompress.begin(); sit != origFilesToCompress.end(); ++sit)
  303. {
  304. std::string tmp = sourceDirName;
  305. tmp.append("\\");
  306. tmp.append(*sit);
  307. tmp.append("\n");
  308. DEBUG_LOG(("Compressing file: %s", tmp.c_str()));
  309. DWORD len;
  310. WriteFile(h, tmp.c_str(), tmp.length(), &len, NULL);
  311. }
  312. CloseHandle(h);
  313. std::string commandLine;
  314. commandLine = "\\projects\\rts\\build\\nvdxt -list ";
  315. commandLine.append(tmpFname);
  316. commandLine.append(" -24 dxt1c -32 dxt5 -full -outdir ");
  317. commandLine.append(cacheDirName);
  318. commandLine.append(" > ");
  319. commandLine.append(dxtOutFname);
  320. DEBUG_LOG(("Compressing textures with command line of '%s'\n", commandLine.c_str()));
  321. int ret = system(commandLine.c_str());
  322. DEBUG_LOG(("system(%s) returned %d\n", commandLine.c_str(), ret));
  323. DeleteFile(tmpFname);
  324. // now copy compressed file to target dir
  325. for (sit = origFilesToCompress.begin(); sit != origFilesToCompress.end(); ++sit)
  326. {
  327. std::string orig = sourceDirName;
  328. orig.append("\\");
  329. orig.append(*sit);
  330. struct stat origStat;
  331. stat( orig.c_str(), &origStat);
  332. struct _utimbuf utb;
  333. utb.actime = origStat.st_atime;
  334. utb.modtime = origStat.st_mtime;
  335. std::string src = cacheDirName;
  336. src.append("\\");
  337. src.append(*sit);
  338. src.replace(src.size()-4, 4, ".dds");
  339. _utime(src.c_str(), &utb);
  340. std::string dest = targetDirName;
  341. dest.append("\\");
  342. dest.append(*sit);
  343. dest.replace(dest.size()-4, 4, ".dds");
  344. DEBUG_LOG(("Copying new file from %s to %s\n", src.c_str(), dest.c_str()));
  345. if (_chmod(dest.c_str(), _S_IWRITE | _S_IREAD) == -1)
  346. {
  347. DEBUG_LOG(("Cannot chmod '%s'\n", dest.c_str()));
  348. }
  349. BOOL ret = CopyFile(src.c_str(), dest.c_str(), FALSE);
  350. if (!ret)
  351. {
  352. DEBUG_LOG(("Could not copy file!\n"));
  353. }
  354. _utime(dest.c_str(), &utb);
  355. }
  356. }
  357. //-------------------------------------------------------------------------------------------------
  358. void copyOrigFiles(const std::string& sourceDirName, const std::string& targetDirName, const std::string& cacheDirName,
  359. StringSet& origFilesToCopy)
  360. {
  361. StringSet::const_iterator sit;
  362. for (sit = origFilesToCopy.begin(); sit != origFilesToCopy.end(); ++sit)
  363. {
  364. std::string src = sourceDirName;
  365. src.append("\\");
  366. src.append(*sit);
  367. std::string dest = targetDirName;
  368. dest.append("\\");
  369. dest.append(*sit);
  370. if (_chmod(dest.c_str(), _S_IWRITE | _S_IREAD) == -1)
  371. {
  372. DEBUG_LOG(("Cannot chmod '%s'\n", dest.c_str()));
  373. }
  374. BOOL res = CopyFile(src.c_str(), dest.c_str(), FALSE);
  375. DEBUG_LOG(("Copying file: %s returns %d\n", src.c_str(), res));
  376. }
  377. }
  378. //-------------------------------------------------------------------------------------------------
  379. static void scanDir( const std::string& sourceDirName, const std::string& targetDirName, const std::string& cacheDirName, const std::string& dxtOutFname )
  380. {
  381. DEBUG_LOG(("Scanning '%s'\n", sourceDirName.c_str()));
  382. Directory sourceDir(sourceDirName);
  383. DEBUG_LOG(("Scanning '%s'\n", targetDirName.c_str()));
  384. Directory targetDir(targetDirName);
  385. DEBUG_LOG(("Scanning '%s'\n", cacheDirName.c_str()));
  386. Directory cacheDir(cacheDirName);
  387. FileInfoSet *sourceFiles = sourceDir.getFiles();
  388. FileInfoSet *cacheFiles = cacheDir.getFiles();
  389. FileInfoSet *targetFiles = targetDir.getFiles();
  390. StringSet cachedFilesToErase;
  391. StringSet cachedFilesToCopy;
  392. StringSet origFilesToCompress;
  393. StringSet origFilesToCopy;
  394. DEBUG_LOG(("Emptying targetDir\n"));
  395. for (FileInfoSet::iterator targetIt = targetFiles->begin(); targetIt != targetFiles->end(); ++targetIt)
  396. {
  397. FileInfo f = *targetIt;
  398. std::string fname = f.filename;
  399. f.filename.replace(f.filename.size()-4, 4, ".tga");
  400. FileInfoSet::iterator fit = sourceFiles->find(f);
  401. if (fit == sourceFiles->end())
  402. {
  403. // look for pre-existing dds files too
  404. f.filename.replace(f.filename.size()-4, 4, ".dds");
  405. FileInfoSet::iterator ddsfit = sourceFiles->find(f);
  406. if (ddsfit == sourceFiles->end())
  407. {
  408. fname.insert(0, "\\");
  409. fname.insert(0, targetDirName);
  410. DEBUG_LOG(("Deleting now-removed file '%s'\n", fname.c_str()));
  411. DeleteFile(fname.c_str());
  412. }
  413. }
  414. }
  415. for (FileInfoSet::iterator cacheIt = cacheFiles->begin(); cacheIt != cacheFiles->end(); ++cacheIt)
  416. {
  417. FileInfo f = *cacheIt;
  418. int len = f.filename.size();
  419. if (len < 5)
  420. {
  421. cachedFilesToErase.insert(f.filename);
  422. continue;
  423. }
  424. std::string fname = f.filename;
  425. f.filename.replace(len-4, 4, ".tga");
  426. FileInfoSet::iterator fit = sourceFiles->find(f);
  427. if (fit != sourceFiles->end())
  428. {
  429. FileInfo sf = *fit;
  430. if (f.modTime < sf.modTime)
  431. {
  432. /**
  433. std::string orig = sourceDirName;
  434. orig.append("\\");
  435. orig.append(sf.filename);
  436. struct stat origStat;
  437. stat( orig.c_str(), &origStat);
  438. struct _utimbuf utb;
  439. utb.actime = origStat.st_atime;
  440. utb.modtime = origStat.st_mtime;
  441. std::string dest = cacheDirName;
  442. dest.append("\\");
  443. dest.append(f.filename);
  444. dest.replace(dest.size()-4, 4, ".dds");
  445. _utime(dest.c_str(), &utb);
  446. cachedFilesToCopy.insert(fname);
  447. /**/
  448. cachedFilesToErase.insert(fname);
  449. }
  450. else
  451. {
  452. f.filename = fname; // back to .dds
  453. FileInfoSet::iterator it = targetFiles->find(f);
  454. if (it == targetFiles->end())
  455. cachedFilesToCopy.insert(fname);
  456. }
  457. }
  458. else
  459. {
  460. cachedFilesToErase.insert(fname);
  461. }
  462. }
  463. for (FileInfoSet::iterator sourceIt = sourceFiles->begin(); sourceIt != sourceFiles->end(); ++sourceIt)
  464. {
  465. FileInfo f = *sourceIt;
  466. std::string fname = f.filename;
  467. const char *s = fname.c_str();
  468. int index = 0;
  469. const char *check = nodxtPrefix[0];
  470. bool shouldSkip = false;
  471. while (check)
  472. {
  473. if (fname.find(check) == 0)
  474. {
  475. shouldSkip = true;
  476. break;
  477. }
  478. check = nodxtPrefix[++index];
  479. }
  480. index = 0;
  481. check = nodxtAnywhere[0];
  482. while (check && !shouldSkip)
  483. {
  484. if (fname.find(check) != fname.npos)
  485. {
  486. shouldSkip = true;
  487. break;
  488. }
  489. check = nodxtAnywhere[++index];
  490. }
  491. if (!shouldSkip)
  492. {
  493. // check for preexisting .dds files so we can just copy them
  494. if (fname.find(".dds") != fname.npos)
  495. {
  496. shouldSkip = true;
  497. }
  498. }
  499. if (shouldSkip)
  500. {
  501. origFilesToCopy.insert(s);
  502. }
  503. else
  504. {
  505. int len = f.filename.size();
  506. f.filename.replace(len-4, 4, ".dds");
  507. FileInfoSet::iterator fit = cacheFiles->find(f);
  508. if (fit != cacheFiles->end())
  509. {
  510. FileInfo cf = *fit;
  511. if (cf.modTime < f.modTime)
  512. {
  513. origFilesToCompress.insert(fname);
  514. }
  515. }
  516. else
  517. {
  518. origFilesToCompress.insert(fname);
  519. }
  520. }
  521. }
  522. // now dump our files
  523. eraseCachedFiles (sourceDirName, targetDirName, cacheDirName, cachedFilesToErase);
  524. copyCachedFiles (sourceDirName, targetDirName, cacheDirName, cachedFilesToCopy);
  525. copyOrigFiles (sourceDirName, targetDirName, cacheDirName, origFilesToCopy);
  526. compressOrigFiles(sourceDirName, targetDirName, cacheDirName, origFilesToCompress, dxtOutFname);
  527. }
  528. //-------------------------------------------------------------------------------------------------
  529. #define USE_WINMAIN
  530. #ifdef USE_WINMAIN
  531. int APIENTRY WinMain(HINSTANCE hInstance,
  532. HINSTANCE hPrevInstance,
  533. LPSTR lpCmdLine,
  534. int nCmdShow)
  535. {
  536. /*
  537. ** Convert WinMain arguments to simple main argc and argv
  538. */
  539. int argc = 1;
  540. char * argv[20];
  541. argv[0] = NULL;
  542. char * token = strtok(lpCmdLine, " ");
  543. while (argc < 20 && token != NULL)
  544. {
  545. argv[argc++] = strtrim(token);
  546. token = strtok(NULL, " ");
  547. }
  548. #else
  549. int main(int argc, const char **argv)
  550. {
  551. #endif // USE_WINMAIN
  552. if (argc != 6)
  553. {
  554. usage(argv[0]);
  555. }
  556. else
  557. {
  558. const char *sourceDir = argv[1];
  559. const char *targetDir = argv[2];
  560. const char *cacheDir = argv[3];
  561. #ifndef NDEBUG
  562. theDebugMunkee = new DebugMunkee(argv[4]);
  563. #endif
  564. //setUpLoadWindow();
  565. scanDir(sourceDir, targetDir, cacheDir, argv[5]);
  566. //setLoadWindowText("Writing to file...");
  567. //printSet( noAlphaChannel, "No Alpha Channel" );
  568. //printSet( noAlpha, "Not Using Alpha Channel" );
  569. //printSet( hasAlpha, "Using Alpha Channel" );
  570. //tearDownLoadWindow();
  571. #ifndef NDEBUG
  572. delete theDebugMunkee;
  573. theDebugMunkee = NULL;
  574. #endif
  575. }
  576. return 0;
  577. }