CompressionManager.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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: Compression.cpp /////////////////////////////////////////////////////
  19. // Author: Matthew D. Campbell
  20. // LZH wrapper taken from Nox, originally from Jeff Brown
  21. //////////////////////////////////////////////////////////////////////////////
  22. #include "Compression.h"
  23. #include "LZHCompress/NoxCompress.h"
  24. extern "C" {
  25. #include "ZLib/zlib.h"
  26. }
  27. #include "EAC/codex.h"
  28. #include "EAC/btreecodex.h"
  29. #include "EAC/huffcodex.h"
  30. #include "EAC/refcodex.h"
  31. #ifdef _INTERNAL
  32. // for occasional debugging...
  33. //#pragma optimize("", off)
  34. //#pragma message("************************************** WARNING, optimization disabled for debugging purposes")
  35. #endif
  36. #define DEBUG_LOG(x) {}
  37. const char *CompressionManager::getCompressionNameByType( CompressionType compType )
  38. {
  39. static const char *s_compressionNames[COMPRESSION_MAX+1] = {
  40. "No compression",
  41. "RefPack",
  42. /*
  43. "LZHL",
  44. "ZLib 1 (fast)",
  45. "ZLib 2",
  46. "ZLib 3",
  47. "ZLib 4",
  48. "ZLib 5 (default)",
  49. "ZLib 6",
  50. "ZLib 7",
  51. "ZLib 8",
  52. "ZLib 9 (slow)",
  53. "BTree",
  54. "Huff",
  55. */
  56. };
  57. return s_compressionNames[compType];
  58. }
  59. // For perf timers, so we can have separate ones for compression/decompression
  60. const char *CompressionManager::getDecompressionNameByType( CompressionType compType )
  61. {
  62. static const char *s_decompressionNames[COMPRESSION_MAX+1] = {
  63. "d_None",
  64. "d_RefPack",
  65. /*
  66. "d_NoxLZW",
  67. "d_ZLib1",
  68. "d_ZLib2",
  69. "d_ZLib3",
  70. "d_ZLib4",
  71. "d_ZLib5",
  72. "d_ZLib6",
  73. "d_ZLib7",
  74. "d_ZLib8",
  75. "d_ZLib9",
  76. "d_BTree",
  77. "d_Huff",
  78. */
  79. };
  80. return s_decompressionNames[compType];
  81. }
  82. // ---------------------------------------------------------------------------------------
  83. Bool CompressionManager::isDataCompressed( const void *mem, Int len )
  84. {
  85. CompressionType t = getCompressionType(mem, len);
  86. return t != COMPRESSION_NONE;
  87. }
  88. CompressionType CompressionManager::getPreferredCompression( void )
  89. {
  90. return COMPRESSION_REFPACK;
  91. }
  92. CompressionType CompressionManager::getCompressionType( const void *mem, Int len )
  93. {
  94. if (len < 8)
  95. return COMPRESSION_NONE;
  96. if ( memcmp( mem, "NOX\0", 4 ) == 0 )
  97. return COMPRESSION_NOXLZH;
  98. if ( memcmp( mem, "ZL1\0", 4 ) == 0 )
  99. return COMPRESSION_ZLIB1;
  100. if ( memcmp( mem, "ZL2\0", 4 ) == 0 )
  101. return COMPRESSION_ZLIB2;
  102. if ( memcmp( mem, "ZL3\0", 4 ) == 0 )
  103. return COMPRESSION_ZLIB3;
  104. if ( memcmp( mem, "ZL4\0", 4 ) == 0 )
  105. return COMPRESSION_ZLIB4;
  106. if ( memcmp( mem, "ZL5\0", 4 ) == 0 )
  107. return COMPRESSION_ZLIB5;
  108. if ( memcmp( mem, "ZL6\0", 4 ) == 0 )
  109. return COMPRESSION_ZLIB6;
  110. if ( memcmp( mem, "ZL7\0", 4 ) == 0 )
  111. return COMPRESSION_ZLIB7;
  112. if ( memcmp( mem, "ZL8\0", 4 ) == 0 )
  113. return COMPRESSION_ZLIB8;
  114. if ( memcmp( mem, "ZL9\0", 4 ) == 0 )
  115. return COMPRESSION_ZLIB9;
  116. if ( memcmp( mem, "EAB\0", 4 ) == 0 )
  117. return COMPRESSION_BTREE;
  118. if ( memcmp( mem, "EAH\0", 4 ) == 0 )
  119. return COMPRESSION_HUFF;
  120. if ( memcmp( mem, "EAR\0", 4 ) == 0 )
  121. return COMPRESSION_REFPACK;
  122. return COMPRESSION_NONE;
  123. }
  124. Int CompressionManager::getMaxCompressedSize( Int uncompressedLen, CompressionType compType )
  125. {
  126. switch (compType)
  127. {
  128. case COMPRESSION_NOXLZH:
  129. return CalcNewSize(uncompressedLen) + 8;
  130. case COMPRESSION_BTREE: // guessing here
  131. case COMPRESSION_HUFF: // guessing here
  132. case COMPRESSION_REFPACK: // guessing here
  133. return uncompressedLen + 8;
  134. case COMPRESSION_ZLIB1:
  135. case COMPRESSION_ZLIB2:
  136. case COMPRESSION_ZLIB3:
  137. case COMPRESSION_ZLIB4:
  138. case COMPRESSION_ZLIB5:
  139. case COMPRESSION_ZLIB6:
  140. case COMPRESSION_ZLIB7:
  141. case COMPRESSION_ZLIB8:
  142. case COMPRESSION_ZLIB9:
  143. return (Int)(ceil(uncompressedLen * 1.1 + 12 + 8));
  144. }
  145. return 0;
  146. }
  147. Int CompressionManager::getUncompressedSize( const void *mem, Int len )
  148. {
  149. if (len < 8)
  150. return len;
  151. CompressionType compType = getCompressionType( mem, len );
  152. switch (compType)
  153. {
  154. case COMPRESSION_NOXLZH:
  155. case COMPRESSION_ZLIB1:
  156. case COMPRESSION_ZLIB2:
  157. case COMPRESSION_ZLIB3:
  158. case COMPRESSION_ZLIB4:
  159. case COMPRESSION_ZLIB5:
  160. case COMPRESSION_ZLIB6:
  161. case COMPRESSION_ZLIB7:
  162. case COMPRESSION_ZLIB8:
  163. case COMPRESSION_ZLIB9:
  164. case COMPRESSION_BTREE:
  165. case COMPRESSION_HUFF:
  166. case COMPRESSION_REFPACK:
  167. return *(Int *)(((UnsignedByte *)mem)+4);
  168. }
  169. return len;
  170. }
  171. Int CompressionManager::compressData( CompressionType compType, void *srcVoid, Int srcLen, void *destVoid, Int destLen )
  172. {
  173. if (destLen < 8)
  174. return 0;
  175. destLen -= 8;
  176. UnsignedByte *src = (UnsignedByte *)srcVoid;
  177. UnsignedByte *dest = (UnsignedByte *)destVoid;
  178. if (compType == COMPRESSION_BTREE)
  179. {
  180. memcpy(dest, "EAB\0", 4);
  181. *(Int *)(dest+4) = 0;
  182. Int ret = BTREE_encode(dest+8, src, srcLen);
  183. if (ret)
  184. {
  185. *(Int *)(dest+4) = srcLen;
  186. return ret + 8;
  187. }
  188. else
  189. return 0;
  190. }
  191. if (compType == COMPRESSION_HUFF)
  192. {
  193. memcpy(dest, "EAH\0", 4);
  194. *(Int *)(dest+4) = 0;
  195. Int ret = HUFF_encode(dest+8, src, srcLen);
  196. if (ret)
  197. {
  198. *(Int *)(dest+4) = srcLen;
  199. return ret + 8;
  200. }
  201. else
  202. return 0;
  203. }
  204. if (compType == COMPRESSION_REFPACK)
  205. {
  206. memcpy(dest, "EAR\0", 4);
  207. *(Int *)(dest+4) = 0;
  208. Int ret = REF_encode(dest+8, src, srcLen);
  209. if (ret)
  210. {
  211. *(Int *)(dest+4) = srcLen;
  212. return ret + 8;
  213. }
  214. else
  215. return 0;
  216. }
  217. if (compType == COMPRESSION_NOXLZH)
  218. {
  219. memcpy(dest, "NOX\0", 4);
  220. *(Int *)(dest+4) = 0;
  221. Bool ret = CompressMemory(src, srcLen, dest+8, destLen);
  222. if (ret)
  223. {
  224. *(Int *)(dest+4) = srcLen;
  225. return destLen + 8;
  226. }
  227. else
  228. return 0;
  229. }
  230. if (compType >= COMPRESSION_ZLIB1 && compType <= COMPRESSION_ZLIB9)
  231. {
  232. Int level = compType - COMPRESSION_ZLIB1 + 1; // 1-9
  233. memcpy(dest, "ZL0\0", 4);
  234. dest[2] = '0' + level;
  235. *(Int *)(dest+4) = 0;
  236. unsigned long outLen = destLen;
  237. Int err = z_compress2( dest+8, &outLen, src, srcLen, level );
  238. if (err == Z_OK || err == Z_STREAM_END)
  239. {
  240. *(Int *)(dest+4) = srcLen;
  241. return outLen + 8;
  242. }
  243. else
  244. {
  245. DEBUG_LOG(("ZLib compression error (level is %d, src len is %d) %d\n", level, srcLen, err));
  246. return 0;
  247. }
  248. }
  249. return 0;
  250. }
  251. Int CompressionManager::decompressData( void *srcVoid, Int srcLen, void *destVoid, Int destLen )
  252. {
  253. if (srcLen < 8)
  254. return 0;
  255. UnsignedByte *src = (UnsignedByte *)srcVoid;
  256. UnsignedByte *dest = (UnsignedByte *)destVoid;
  257. CompressionType compType = getCompressionType(src, srcLen);
  258. if (compType == COMPRESSION_BTREE)
  259. {
  260. Int slen = srcLen - 8;
  261. Int ret = BTREE_decode(dest, src+8, &slen);
  262. if (ret)
  263. return ret;
  264. else
  265. return 0;
  266. }
  267. if (compType == COMPRESSION_HUFF)
  268. {
  269. Int slen = srcLen - 8;
  270. Int ret = HUFF_decode(dest, src+8, &slen);
  271. if (ret)
  272. return ret;
  273. else
  274. return 0;
  275. }
  276. if (compType == COMPRESSION_REFPACK)
  277. {
  278. Int slen = srcLen - 8;
  279. Int ret = REF_decode(dest, src+8, &slen);
  280. if (ret)
  281. return ret;
  282. else
  283. return 0;
  284. }
  285. if (compType == COMPRESSION_NOXLZH)
  286. {
  287. Bool ret = DecompressMemory(src+8, srcLen-8, dest, destLen);
  288. if (ret)
  289. return destLen;
  290. else
  291. return 0;
  292. }
  293. if (compType >= COMPRESSION_ZLIB1 && compType <= COMPRESSION_ZLIB9)
  294. {
  295. #ifdef DEBUG_LOGGING
  296. Int level = compType - COMPRESSION_ZLIB1 + 1; // 1-9
  297. #endif
  298. unsigned long outLen = destLen;
  299. Int err = z_uncompress(dest, &outLen, src+8, srcLen-8);
  300. if (err == Z_OK || err == Z_STREAM_END)
  301. {
  302. return outLen;
  303. }
  304. else
  305. {
  306. DEBUG_LOG(("ZLib decompression error (src is level %d, %d bytes long) %d\n", level, srcLen, err));
  307. return 0;
  308. }
  309. }
  310. return 0;
  311. }
  312. ///////////////////////////////////////////////////////////////////////////////////////////
  313. ///////////////////////////////////////////////////////////////////////////////////////////
  314. ///// Performance Testing ///////////////////////////////////////////////////////////////
  315. ///////////////////////////////////////////////////////////////////////////////////////////
  316. ///////////////////////////////////////////////////////////////////////////////////////////
  317. #ifdef TEST_COMPRESSION
  318. #include "GameClient/MapUtil.h"
  319. #include "Common/FileSystem.h"
  320. #include "Common/File.h"
  321. #include "Common/PerfTimer.h"
  322. enum { NUM_TIMES = 10 };
  323. struct CompData
  324. {
  325. public:
  326. Int origSize;
  327. Int compressedSize[COMPRESSION_MAX+1];
  328. };
  329. void DoCompressTest( void )
  330. {
  331. Int i;
  332. PerfGather *s_compressGathers[COMPRESSION_MAX+1];
  333. PerfGather *s_decompressGathers[COMPRESSION_MAX+1];
  334. for (i = 0; i < COMPRESSION_MAX+1; ++i)
  335. {
  336. s_compressGathers[i] = new PerfGather(CompressionManager::getCompressionNameByType((CompressionType)i));
  337. s_decompressGathers[i] = new PerfGather(CompressionManager::getDecompressionNameByType((CompressionType)i));
  338. }
  339. std::map<AsciiString, CompData> s_sizes;
  340. std::map<AsciiString, MapMetaData>::const_iterator it = TheMapCache->begin();
  341. while (it != TheMapCache->end())
  342. {
  343. //if (it->second.m_isOfficial)
  344. //{
  345. //++it;
  346. //continue;
  347. //}
  348. //static Int count = 0;
  349. //if (count++ > 2)
  350. //break;
  351. File *f = TheFileSystem->openFile(it->first.str());
  352. if (f)
  353. {
  354. DEBUG_LOG(("***************************\nTesting '%s'\n\n", it->first.str()));
  355. Int origSize = f->size();
  356. UnsignedByte *buf = (UnsignedByte *)f->readEntireAndClose();
  357. UnsignedByte *uncompressedBuf = NEW UnsignedByte[origSize];
  358. CompData d = s_sizes[it->first];
  359. d.origSize = origSize;
  360. d.compressedSize[COMPRESSION_NONE] = origSize;
  361. for (i=COMPRESSION_MIN; i<=COMPRESSION_MAX; ++i)
  362. {
  363. DEBUG_LOG(("=================================================\n"));
  364. DEBUG_LOG(("Compression Test %d\n", i));
  365. Int maxCompressedSize = CompressionManager::getMaxCompressedSize( origSize, (CompressionType)i );
  366. DEBUG_LOG(("Orig size is %d, max compressed size is %d bytes\n", origSize, maxCompressedSize));
  367. UnsignedByte *compressedBuf = NEW UnsignedByte[maxCompressedSize];
  368. memset(compressedBuf, 0, maxCompressedSize);
  369. memset(uncompressedBuf, 0, origSize);
  370. Int compressedLen, decompressedLen;
  371. for (Int j=0; j < NUM_TIMES; ++j)
  372. {
  373. s_compressGathers[i]->startTimer();
  374. compressedLen = CompressionManager::compressData((CompressionType)i, buf, origSize, compressedBuf, maxCompressedSize);
  375. s_compressGathers[i]->stopTimer();
  376. s_decompressGathers[i]->startTimer();
  377. decompressedLen = CompressionManager::decompressData(compressedBuf, compressedLen, uncompressedBuf, origSize);
  378. s_decompressGathers[i]->stopTimer();
  379. }
  380. d.compressedSize[i] = compressedLen;
  381. DEBUG_LOG(("Compressed len is %d (%g%% of original size)\n", compressedLen, (double)compressedLen/(double)origSize*100.0));
  382. DEBUG_ASSERTCRASH(compressedLen, ("Failed to compress\n"));
  383. DEBUG_LOG(("Decompressed len is %d (%g%% of original size)\n", decompressedLen, (double)decompressedLen/(double)origSize*100.0));
  384. DEBUG_ASSERTCRASH(decompressedLen == origSize, ("orig size does not match compressed+uncompressed output\n"));
  385. if (decompressedLen == origSize)
  386. {
  387. Int ret = memcmp(buf, uncompressedBuf, origSize);
  388. if (ret != 0)
  389. {
  390. DEBUG_CRASH(("orig buffer does not match compressed+uncompressed output - ret was %d\n", ret));
  391. }
  392. }
  393. delete compressedBuf;
  394. compressedBuf = NULL;
  395. }
  396. DEBUG_LOG(("d = %d -> %d\n", d.origSize, d.compressedSize[i]));
  397. s_sizes[it->first] = d;
  398. DEBUG_LOG(("s_sizes[%s] = %d -> %d\n", it->first.str(), s_sizes[it->first].origSize, s_sizes[it->first].compressedSize[i]));
  399. delete[] buf;
  400. buf = NULL;
  401. delete[] uncompressedBuf;
  402. uncompressedBuf = NULL;
  403. }
  404. ++it;
  405. }
  406. for (i=COMPRESSION_MIN; i<=COMPRESSION_MAX; ++i)
  407. {
  408. Real maxCompression = 1000.0f;
  409. Real minCompression = 0.0f;
  410. Int totalUncompressedBytes = 0;
  411. Int totalCompressedBytes = 0;
  412. for (std::map<AsciiString, CompData>::iterator cd = s_sizes.begin(); cd != s_sizes.end(); ++cd)
  413. {
  414. CompData d = cd->second;
  415. Real ratio = d.compressedSize[i]/(Real)d.origSize;
  416. maxCompression = min(maxCompression, ratio);
  417. minCompression = max(minCompression, ratio);
  418. totalUncompressedBytes += d.origSize;
  419. totalCompressedBytes += d.compressedSize[i];
  420. }
  421. DEBUG_LOG(("***************************************************\n"));
  422. DEBUG_LOG(("Compression method %s:\n", CompressionManager::getCompressionNameByType((CompressionType)i)));
  423. DEBUG_LOG(("%d bytes compressed to %d (%g%%)\n", totalUncompressedBytes, totalCompressedBytes,
  424. totalCompressedBytes/(Real)totalUncompressedBytes*100.0f));
  425. DEBUG_LOG(("Min ratio: %g%%, Max ratio: %g%%\n",
  426. minCompression*100.0f, maxCompression*100.0f));
  427. DEBUG_LOG(("\n"));
  428. }
  429. PerfGather::dumpAll(10000);
  430. //PerfGather::displayGraph(TheGameLogic->getFrame());
  431. PerfGather::resetAll();
  432. CopyFile( "AAAPerfStats.csv", "AAACompressPerfStats.csv", FALSE );
  433. for (i = 0; i < COMPRESSION_MAX+1; ++i)
  434. {
  435. delete s_compressGathers[i];
  436. s_compressGathers[i] = NULL;
  437. delete s_decompressGathers[i];
  438. s_decompressGathers[i] = NULL;
  439. }
  440. }
  441. #endif // TEST_COMPRESSION