PageRenderTime 47ms CodeModel.GetById 8ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/ghost/packed.cpp

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