/ghost/packed.cpp

http://ghostcb.googlecode.com/ · C++ · 394 lines · 264 code · 81 blank · 49 comment · 43 complexity · 76b3151fbb6169839689579f1466de34 MD5 · raw file

  1. /*
  2. Copyright [2008] [Trevor Hogan]
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/
  13. */
  14. #include "ghost.h"
  15. #include "util.h"
  16. #include "crc32.h"
  17. #include "packed.h"
  18. #include <zlib.h>
  19. // we can't use zlib's uncompress function because it expects a complete compressed buffer
  20. // however, we're going to be passing it chunks of incomplete data
  21. // this custom tzuncompress function will do the job
  22. int tzuncompress( Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen )
  23. {
  24. z_stream stream;
  25. int err;
  26. stream.next_in = (Bytef*)source;
  27. stream.avail_in = (uInt)sourceLen;
  28. /* Check for source > 64K on 16-bit machine: */
  29. if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
  30. stream.next_out = dest;
  31. stream.avail_out = (uInt)*destLen;
  32. if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
  33. stream.zalloc = (alloc_func)0;
  34. stream.zfree = (free_func)0;
  35. err = inflateInit(&stream);
  36. if (err != Z_OK) return err;
  37. err = inflate(&stream, Z_SYNC_FLUSH);
  38. if (err != Z_STREAM_END && err != Z_OK) {
  39. inflateEnd(&stream);
  40. if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
  41. return Z_DATA_ERROR;
  42. return err;
  43. }
  44. *destLen = stream.total_out;
  45. err = inflateEnd(&stream);
  46. return err;
  47. }
  48. //
  49. // CPacked
  50. //
  51. CPacked :: CPacked( ) : m_Valid( true ), m_HeaderSize( 0 ), m_CompressedSize( 0 ), m_HeaderVersion( 0 ), m_DecompressedSize( 0 ), m_NumBlocks( 0 ), m_War3Identifier( 0 ), m_War3Version( 0 ), m_BuildNumber( 0 ), m_Flags( 0 ), m_ReplayLength( 0 )
  52. {
  53. m_CRC = new CCRC32( );
  54. m_CRC->Initialize( );
  55. }
  56. CPacked :: ~CPacked( )
  57. {
  58. delete m_CRC;
  59. }
  60. void CPacked :: Load( string fileName, bool allBlocks )
  61. {
  62. m_Valid = true;
  63. CONSOLE_Print( "[PACKED] loading data from file [" + fileName + "]" );
  64. m_Compressed = UTIL_FileRead( fileName );
  65. Decompress( allBlocks );
  66. }
  67. bool CPacked :: Save( bool TFT, string fileName )
  68. {
  69. Compress( TFT );
  70. if( m_Valid )
  71. {
  72. CONSOLE_Print( "[PACKED] saving data to file [" + fileName + "]" );
  73. return UTIL_FileWrite( fileName, (unsigned char *)m_Compressed.c_str( ), m_Compressed.size( ) );
  74. }
  75. else
  76. return false;
  77. }
  78. bool CPacked :: Extract( string inFileName, string outFileName )
  79. {
  80. m_Valid = true;
  81. CONSOLE_Print( "[PACKED] extracting data from file [" + inFileName + "] to file [" + outFileName + "]" );
  82. m_Compressed = UTIL_FileRead( inFileName );
  83. Decompress( true );
  84. if( m_Valid )
  85. return UTIL_FileWrite( outFileName, (unsigned char *)m_Decompressed.c_str( ), m_Decompressed.size( ) );
  86. else
  87. return false;
  88. }
  89. bool CPacked :: Pack( bool TFT, string inFileName, string outFileName )
  90. {
  91. m_Valid = true;
  92. CONSOLE_Print( "[PACKET] packing data from file [" + inFileName + "] to file [" + outFileName + "]" );
  93. m_Decompressed = UTIL_FileRead( inFileName );
  94. Compress( TFT );
  95. if( m_Valid )
  96. return UTIL_FileWrite( outFileName, (unsigned char *)m_Compressed.c_str( ), m_Compressed.size( ) );
  97. else
  98. return false;
  99. }
  100. void CPacked :: Decompress( bool allBlocks )
  101. {
  102. CONSOLE_Print( "[PACKED] decompressing data" );
  103. // format found at http://www.thehelper.net/forums/showthread.php?t=42787
  104. m_Decompressed.clear( );
  105. istringstream ISS( m_Compressed );
  106. string GarbageString;
  107. // read header
  108. getline( ISS, GarbageString, '\0' );
  109. if( GarbageString != "Warcraft III recorded game\x01A" )
  110. {
  111. CONSOLE_Print( "[PACKED] not a valid packed file" );
  112. m_Valid = false;
  113. return;
  114. }
  115. ISS.read( (char *)&m_HeaderSize, 4 ); // header size
  116. ISS.read( (char *)&m_CompressedSize, 4 ); // compressed file size
  117. ISS.read( (char *)&m_HeaderVersion, 4 ); // header version
  118. ISS.read( (char *)&m_DecompressedSize, 4 ); // decompressed file size
  119. ISS.read( (char *)&m_NumBlocks, 4 ); // number of blocks
  120. if( m_HeaderVersion == 0 )
  121. {
  122. ISS.seekg( 2, ios :: cur ); // unknown
  123. ISS.seekg( 2, ios :: cur ); // version number
  124. CONSOLE_Print( "[PACKED] header version is too old" );
  125. m_Valid = false;
  126. return;
  127. }
  128. else
  129. {
  130. ISS.read( (char *)&m_War3Identifier, 4 ); // version identifier
  131. ISS.read( (char *)&m_War3Version, 4 ); // version number
  132. }
  133. ISS.read( (char *)&m_BuildNumber, 2 ); // build number
  134. ISS.read( (char *)&m_Flags, 2 ); // flags
  135. ISS.read( (char *)&m_ReplayLength, 4 ); // replay length
  136. ISS.seekg( 4, ios :: cur ); // CRC
  137. if( ISS.fail( ) )
  138. {
  139. CONSOLE_Print( "[PACKED] failed to read header" );
  140. m_Valid = false;
  141. return;
  142. }
  143. if( allBlocks )
  144. CONSOLE_Print( "[PACKED] reading " + UTIL_ToString( m_NumBlocks ) + " blocks" );
  145. else
  146. CONSOLE_Print( "[PACKED] reading 1/" + UTIL_ToString( m_NumBlocks ) + " blocks" );
  147. // read blocks
  148. for( uint32_t i = 0; i < m_NumBlocks; ++i )
  149. {
  150. uint16_t BlockCompressed;
  151. uint16_t BlockDecompressed;
  152. // read block header
  153. ISS.read( (char *)&BlockCompressed, 2 ); // block compressed size
  154. ISS.read( (char *)&BlockDecompressed, 2 ); // block decompressed size
  155. ISS.seekg( 4, ios :: cur ); // checksum
  156. if( ISS.fail( ) )
  157. {
  158. CONSOLE_Print( "[PACKED] failed to read block header" );
  159. m_Valid = false;
  160. return;
  161. }
  162. // read block data
  163. uLongf BlockCompressedLong = BlockCompressed;
  164. uLongf BlockDecompressedLong = BlockDecompressed;
  165. unsigned char *CompressedData = new unsigned char[BlockCompressed];
  166. unsigned char *DecompressedData = new unsigned char[BlockDecompressed];
  167. ISS.read( (char*)CompressedData, BlockCompressed );
  168. if( ISS.fail( ) )
  169. {
  170. CONSOLE_Print( "[PACKED] failed to read block data" );
  171. delete [] DecompressedData;
  172. delete [] CompressedData;
  173. m_Valid = false;
  174. return;
  175. }
  176. // decompress block data
  177. int Result = tzuncompress( DecompressedData, &BlockDecompressedLong, CompressedData, BlockCompressedLong );
  178. if( Result != Z_OK )
  179. {
  180. CONSOLE_Print( "[PACKED] tzuncompress error " + UTIL_ToString( Result ) );
  181. delete [] DecompressedData;
  182. delete [] CompressedData;
  183. m_Valid = false;
  184. return;
  185. }
  186. if( BlockDecompressedLong != (uLongf)BlockDecompressed )
  187. {
  188. CONSOLE_Print( "[PACKED] block decompressed size mismatch, actual = " + UTIL_ToString( BlockDecompressedLong ) + ", expected = " + UTIL_ToString( BlockDecompressed ) );
  189. delete [] DecompressedData;
  190. delete [] CompressedData;
  191. m_Valid = false;
  192. return;
  193. }
  194. m_Decompressed += string( (char *)DecompressedData, BlockDecompressedLong );
  195. delete [] DecompressedData;
  196. delete [] CompressedData;
  197. // stop after one iteration if not decompressing all blocks
  198. if( !allBlocks )
  199. break;
  200. }
  201. CONSOLE_Print( "[PACKED] decompressed " + UTIL_ToString( m_Decompressed.size( ) ) + " bytes" );
  202. if( allBlocks || m_NumBlocks == 1 )
  203. {
  204. if( m_DecompressedSize > m_Decompressed.size( ) )
  205. {
  206. CONSOLE_Print( "[PACKED] not enough decompressed data" );
  207. m_Valid = false;
  208. return;
  209. }
  210. // the last block is padded with zeros, discard them
  211. CONSOLE_Print( "[PACKED] discarding " + UTIL_ToString( m_Decompressed.size( ) - m_DecompressedSize ) + " bytes" );
  212. m_Decompressed.erase( m_DecompressedSize );
  213. }
  214. }
  215. void CPacked :: Compress( bool TFT )
  216. {
  217. CONSOLE_Print( "[PACKED] compressing data" );
  218. // format found at http://www.thehelper.net/forums/showthread.php?t=42787
  219. m_Compressed.clear( );
  220. // compress data into blocks of size 8192 bytes
  221. // use a buffer of size 8213 bytes because in the worst case zlib will grow the data 0.1% plus 12 bytes
  222. uint32_t CompressedSize = 0;
  223. string Padded = m_Decompressed;
  224. Padded.append( 8192 - ( Padded.size( ) % 8192 ), 0 );
  225. vector<string> CompressedBlocks;
  226. string :: size_type Position = 0;
  227. unsigned char *CompressedData = new unsigned char[8213];
  228. while( Position < Padded.size( ) )
  229. {
  230. uLongf BlockCompressedLong = 8213;
  231. int Result = compress( CompressedData, &BlockCompressedLong, (const Bytef *)Padded.c_str( ) + Position, 8192 );
  232. if( Result != Z_OK )
  233. {
  234. CONSOLE_Print( "[PACKED] compress error " + UTIL_ToString( Result ) );
  235. delete [] CompressedData;
  236. m_Valid = false;
  237. return;
  238. }
  239. CompressedBlocks.push_back( string( (char *)CompressedData, BlockCompressedLong ) );
  240. CompressedSize += BlockCompressedLong;
  241. Position += 8192;
  242. }
  243. delete [] CompressedData;
  244. // build header
  245. uint32_t HeaderSize = 68;
  246. uint32_t HeaderCompressedSize = HeaderSize + CompressedSize + CompressedBlocks.size( ) * 8;
  247. uint32_t HeaderVersion = 1;
  248. BYTEARRAY Header;
  249. UTIL_AppendByteArray( Header, "Warcraft III recorded game\x01A" );
  250. UTIL_AppendByteArray( Header, HeaderSize, false );
  251. UTIL_AppendByteArray( Header, HeaderCompressedSize, false );
  252. UTIL_AppendByteArray( Header, HeaderVersion, false );
  253. UTIL_AppendByteArray( Header, (uint32_t)m_Decompressed.size( ), false );
  254. UTIL_AppendByteArray( Header, (uint32_t)CompressedBlocks.size( ), false );
  255. if( TFT )
  256. {
  257. Header.push_back( 'P' ); // "W3XP"
  258. Header.push_back( 'X' );
  259. Header.push_back( '3' );
  260. Header.push_back( 'W' );
  261. }
  262. else
  263. {
  264. Header.push_back( '3' ); // "WAR3"
  265. Header.push_back( 'R' );
  266. Header.push_back( 'A' );
  267. Header.push_back( 'W' );
  268. }
  269. UTIL_AppendByteArray( Header, m_War3Version, false );
  270. UTIL_AppendByteArray( Header, m_BuildNumber, false );
  271. UTIL_AppendByteArray( Header, m_Flags, false );
  272. UTIL_AppendByteArray( Header, m_ReplayLength, false );
  273. // append zero header CRC
  274. // the header CRC is calculated over the entire header with itself set to zero
  275. // we'll overwrite the zero header CRC after we calculate it
  276. UTIL_AppendByteArray( Header, (uint32_t)0, false );
  277. // calculate header CRC
  278. string HeaderString = string( Header.begin( ), Header.end( ) );
  279. uint32_t CRC = m_CRC->FullCRC( (unsigned char *)HeaderString.c_str( ), HeaderString.size( ) );
  280. // overwrite the (currently zero) header CRC with the calculated CRC
  281. Header.erase( Header.end( ) - 4, Header.end( ) );
  282. UTIL_AppendByteArray( Header, CRC, false );
  283. // append header
  284. m_Compressed += string( Header.begin( ), Header.end( ) );
  285. // append blocks
  286. for( vector<string> :: iterator i = CompressedBlocks.begin( ); i != CompressedBlocks.end( ); ++i )
  287. {
  288. BYTEARRAY BlockHeader;
  289. UTIL_AppendByteArray( BlockHeader, (uint16_t)(*i).size( ), false );
  290. UTIL_AppendByteArray( BlockHeader, (uint16_t)8192, false );
  291. // append zero block header CRC
  292. UTIL_AppendByteArray( BlockHeader, (uint32_t)0, false );
  293. // calculate block header CRC
  294. string BlockHeaderString = string( BlockHeader.begin( ), BlockHeader.end( ) );
  295. uint32_t CRC1 = m_CRC->FullCRC( (unsigned char *)BlockHeaderString.c_str( ), BlockHeaderString.size( ) );
  296. CRC1 = CRC1 ^ ( CRC1 >> 16 );
  297. uint32_t CRC2 = m_CRC->FullCRC( (unsigned char *)(*i).c_str( ), (*i).size( ) );
  298. CRC2 = CRC2 ^ ( CRC2 >> 16 );
  299. uint32_t BlockCRC = ( CRC1 & 0xFFFF ) | ( CRC2 << 16 );
  300. // overwrite the block header CRC with the calculated CRC
  301. BlockHeader.erase( BlockHeader.end( ) - 4, BlockHeader.end( ) );
  302. UTIL_AppendByteArray( BlockHeader, BlockCRC, false );
  303. // append block header and data
  304. m_Compressed += string( BlockHeader.begin( ), BlockHeader.end( ) );
  305. m_Compressed += *i;
  306. }
  307. }