/*****************************************************************************/ /* SCompression.cpp Copyright (c) Ladislav Zezula 2003 */ /*---------------------------------------------------------------------------*/ /* This module serves as a bridge between StormLib code and (de)compression */ /* functions. All (de)compression calls go (and should only go) through this */ /* module. No system headers should be included in this module to prevent */ /* compile-time problems. */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ /* 01.04.03 1.00 Lad The first version of SCompression.cpp */ /* 19.11.03 1.01 Dan Big endian handling */ /*****************************************************************************/ #define __STORMLIB_SELF__ #include "StormLib.h" #include "StormCommon.h" //----------------------------------------------------------------------------- // Local structures // Information about the input and output buffers for pklib typedef struct { unsigned char * pbInBuff; // Pointer to input data buffer unsigned char * pbInBuffEnd; // End of the input buffer unsigned char * pbOutBuff; // Pointer to output data buffer unsigned char * pbOutBuffEnd; // Pointer to output data buffer } TDataInfo; // Prototype of the compression function // Function doesn't return an error. A success means that the size of compressed buffer // is lower than size of uncompressed buffer. typedef void (*COMPRESS)( void * pvOutBuffer, // [out] Pointer to the buffer where the compressed data will be stored int * pcbOutBuffer, // [in] Pointer to length of the buffer pointed by pvOutBuffer void * pvInBuffer, // [in] Pointer to the buffer with data to compress int cbInBuffer, // [in] Length of the buffer pointer by pvInBuffer int * pCmpType, // [in] Compression-method specific value. ADPCM Setups this for the following Huffman compression int nCmpLevel); // [in] Compression specific value. ADPCM uses this. Should be set to zero. // Prototype of the decompression function // Returns 1 if success, 0 if failure typedef int (*DECOMPRESS)( void * pvOutBuffer, // [out] Pointer to the buffer where to store decompressed data int * pcbOutBuffer, // [in] Pointer to total size of the buffer pointed by pvOutBuffer // [out] Contains length of the decompressed data void * pvInBuffer, // [in] Pointer to data to be decompressed int cbInBuffer); // [in] Length of the data to be decompressed // Table of compression functions typedef struct { unsigned long uMask; // Compression mask COMPRESS Compress; // Compression function } TCompressTable; // Table of decompression functions typedef struct { unsigned long uMask; // Decompression bit DECOMPRESS Decompress; // Decompression function } TDecompressTable; /*****************************************************************************/ /* */ /* Support for Huffman compression (0x01) */ /* */ /*****************************************************************************/ void Compress_huff(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { THuffmannTree ht(true); TOutputStream os(pvOutBuffer, *pcbOutBuffer); STORMLIB_UNUSED(nCmpLevel); *pcbOutBuffer = ht.Compress(&os, pvInBuffer, cbInBuffer, *pCmpType); } int Decompress_huff(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { THuffmannTree ht(false); TInputStream is(pvInBuffer, cbInBuffer); *pcbOutBuffer = ht.Decompress(pvOutBuffer, *pcbOutBuffer, &is); return (*pcbOutBuffer == 0) ? 0 : 1; } /******************************************************************************/ /* */ /* Support for ZLIB compression (0x02) */ /* */ /******************************************************************************/ void Compress_ZLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { z_stream z; // Stream information for zlib int windowBits; int nResult; // Keep compilers happy STORMLIB_UNUSED(pCmpType); STORMLIB_UNUSED(nCmpLevel); // Fill the stream structure for zlib z.next_in = (Bytef *)pvInBuffer; z.avail_in = (uInt)cbInBuffer; z.total_in = cbInBuffer; z.next_out = (Bytef *)pvOutBuffer; z.avail_out = *pcbOutBuffer; z.total_out = 0; z.zalloc = NULL; z.zfree = NULL; // Determine the proper window bits (WoW.exe build 12694) if(cbInBuffer <= 0x100) windowBits = 8; else if(cbInBuffer <= 0x200) windowBits = 9; else if(cbInBuffer <= 0x400) windowBits = 10; else if(cbInBuffer <= 0x800) windowBits = 11; else if(cbInBuffer <= 0x1000) windowBits = 12; else if(cbInBuffer <= 0x2000) windowBits = 13; else if(cbInBuffer <= 0x4000) windowBits = 14; else windowBits = 15; // Initialize the compression. // Storm.dll uses zlib version 1.1.3 // Wow.exe uses zlib version 1.2.3 nResult = deflateInit2(&z, 6, // Compression level used by WoW MPQs Z_DEFLATED, windowBits, 8, Z_DEFAULT_STRATEGY); if(nResult == Z_OK) { // Call zlib to compress the data nResult = deflate(&z, Z_FINISH); if(nResult == Z_OK || nResult == Z_STREAM_END) *pcbOutBuffer = z.total_out; deflateEnd(&z); } } int Decompress_ZLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { z_stream z; // Stream information for zlib int nResult; // Fill the stream structure for zlib z.next_in = (Bytef *)pvInBuffer; z.avail_in = (uInt)cbInBuffer; z.total_in = cbInBuffer; z.next_out = (Bytef *)pvOutBuffer; z.avail_out = *pcbOutBuffer; z.total_out = 0; z.zalloc = NULL; z.zfree = NULL; // Initialize the decompression structure. Storm.dll uses zlib version 1.1.3 if((nResult = inflateInit(&z)) == Z_OK) { // Call zlib to decompress the data nResult = inflate(&z, Z_FINISH); *pcbOutBuffer = z.total_out; inflateEnd(&z); } return (nResult >= Z_OK); } /******************************************************************************/ /* */ /* Support functions for PKWARE Data Compression Library compression (0x08) */ /* */ /******************************************************************************/ // Function loads data from the input buffer. Used by Pklib's "implode" // and "explode" function as user-defined callback // Returns number of bytes loaded // // char * buf - Pointer to a buffer where to store loaded data // unsigned int * size - Max. number of bytes to read // void * param - Custom pointer, parameter of implode/explode static unsigned int ReadInputData(char * buf, unsigned int * size, void * param) { TDataInfo * pInfo = (TDataInfo *)param; unsigned int nMaxAvail = (unsigned int)(pInfo->pbInBuffEnd - pInfo->pbInBuff); unsigned int nToRead = *size; // Check the case when not enough data available if(nToRead > nMaxAvail) nToRead = nMaxAvail; // Load data and increment offsets memcpy(buf, pInfo->pbInBuff, nToRead); pInfo->pbInBuff += nToRead; assert(pInfo->pbInBuff <= pInfo->pbInBuffEnd); return nToRead; } // Function for store output data. Used by Pklib's "implode" and "explode" // as user-defined callback // // char * buf - Pointer to data to be written // unsigned int * size - Number of bytes to write // void * param - Custom pointer, parameter of implode/explode static void WriteOutputData(char * buf, unsigned int * size, void * param) { TDataInfo * pInfo = (TDataInfo *)param; unsigned int nMaxWrite = (unsigned int)(pInfo->pbOutBuffEnd - pInfo->pbOutBuff); unsigned int nToWrite = *size; // Check the case when not enough space in the output buffer if(nToWrite > nMaxWrite) nToWrite = nMaxWrite; // Write output data and increments offsets memcpy(pInfo->pbOutBuff, buf, nToWrite); pInfo->pbOutBuff += nToWrite; assert(pInfo->pbOutBuff <= pInfo->pbOutBuffEnd); } static void Compress_PKLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { TDataInfo Info; // Data information char * work_buf = STORM_ALLOC(char, CMP_BUFFER_SIZE);// Pklib's work buffer unsigned int dict_size; // Dictionary size unsigned int ctype = CMP_BINARY; // Compression type // Keep compilers happy STORMLIB_UNUSED(pCmpType); STORMLIB_UNUSED(nCmpLevel); // Handle no-memory condition if(work_buf != NULL) { // Fill data information structure memset(work_buf, 0, CMP_BUFFER_SIZE); Info.pbInBuff = (unsigned char *)pvInBuffer; Info.pbInBuffEnd = (unsigned char *)pvInBuffer + cbInBuffer; Info.pbOutBuff = (unsigned char *)pvOutBuffer; Info.pbOutBuffEnd = (unsigned char *)pvOutBuffer + *pcbOutBuffer; // // Set the dictionary size // // Diablo I uses fixed dictionary size of CMP_IMPLODE_DICT_SIZE3 // Starcraft I uses the variable dictionary size based on algorithm below // if (cbInBuffer < 0x600) dict_size = CMP_IMPLODE_DICT_SIZE1; else if(0x600 <= cbInBuffer && cbInBuffer < 0xC00) dict_size = CMP_IMPLODE_DICT_SIZE2; else dict_size = CMP_IMPLODE_DICT_SIZE3; // Do the compression if(implode(ReadInputData, WriteOutputData, work_buf, &Info, &ctype, &dict_size) == CMP_NO_ERROR) *pcbOutBuffer = (int)(Info.pbOutBuff - (unsigned char *)pvOutBuffer); STORM_FREE(work_buf); } } static int Decompress_PKLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { TDataInfo Info; // Data information char * work_buf; int nResult = 0; // Allocate Pklib's work buffer if((work_buf = STORM_ALLOC(char, EXP_BUFFER_SIZE)) != NULL) { // Fill data information structure memset(work_buf, 0, EXP_BUFFER_SIZE); Info.pbInBuff = (unsigned char *)pvInBuffer; Info.pbInBuffEnd = (unsigned char *)pvInBuffer + cbInBuffer; Info.pbOutBuff = (unsigned char *)pvOutBuffer; Info.pbOutBuffEnd = (unsigned char *)pvOutBuffer + *pcbOutBuffer; // Do the decompression if(explode(ReadInputData, WriteOutputData, work_buf, &Info) == CMP_NO_ERROR) nResult = 1; // Give away the number of decompressed bytes *pcbOutBuffer = (int)(Info.pbOutBuff - (unsigned char *)pvOutBuffer); STORM_FREE(work_buf); } return nResult; } /******************************************************************************/ /* */ /* Support for Bzip2 compression (0x10) */ /* */ /******************************************************************************/ static void Compress_BZIP2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { bz_stream strm; int blockSize100k = 9; int workFactor = 30; int bzError; // Keep compilers happy STORMLIB_UNUSED(pCmpType); STORMLIB_UNUSED(nCmpLevel); // Initialize the BZIP2 compression strm.bzalloc = NULL; strm.bzfree = NULL; strm.opaque = NULL; // Blizzard uses 9 as blockSize100k, (0x30 as workFactor) // Last checked on Starcraft II if(BZ2_bzCompressInit(&strm, blockSize100k, 0, workFactor) == BZ_OK) { strm.next_in = (char *)pvInBuffer; strm.avail_in = cbInBuffer; strm.next_out = (char *)pvOutBuffer; strm.avail_out = *pcbOutBuffer; // Perform the compression for(;;) { bzError = BZ2_bzCompress(&strm, (strm.avail_in != 0) ? BZ_RUN : BZ_FINISH); if(bzError == BZ_STREAM_END || bzError < 0) break; } // Put the stream into idle state BZ2_bzCompressEnd(&strm); if(bzError > 0) *pcbOutBuffer = strm.total_out_lo32; } } static int Decompress_BZIP2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { bz_stream strm; int nResult; // Initialize the BZIP2 decompression strm.next_in = (char *)pvInBuffer; strm.avail_in = cbInBuffer; strm.next_out = (char *)pvOutBuffer; strm.avail_out = *pcbOutBuffer; strm.bzalloc = NULL; strm.bzfree = NULL; strm.opaque = NULL; // Initialize decompression if((nResult = BZ2_bzDecompressInit(&strm, 0, 0)) == BZ_OK) { // Perform the decompression nResult = BZ2_bzDecompress(&strm); *pcbOutBuffer = strm.total_out_lo32; BZ2_bzDecompressEnd(&strm); } return (nResult >= BZ_OK); } /******************************************************************************/ /* */ /* Support functions for LZMA compression (0x12) */ /* */ /******************************************************************************/ #define LZMA_HEADER_SIZE (1 + LZMA_PROPS_SIZE + 8) static SRes LZMA_Callback_Progress(void * /* p */, UInt64 /* inSize */, UInt64 /* outSize */) { return SZ_OK; } static void * LZMA_Callback_Alloc(void *p, size_t size) { p = p; return STORM_ALLOC(BYTE, size); } /* address can be 0 */ static void LZMA_Callback_Free(void *p, void *address) { p = p; if(address != NULL) STORM_FREE(address); } // // Note: So far, I haven't seen any files compressed by LZMA. // This code haven't been verified against code ripped from Starcraft II Beta, // but we know that Starcraft LZMA decompression code is able to decompress // the data compressed by StormLib. // static void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { ICompressProgress Progress; CLzmaEncProps props; ISzAlloc SzAlloc; Byte * pbOutBuffer = (Byte *)pvOutBuffer; Byte * destBuffer; SizeT destLen = *pcbOutBuffer; SizeT srcLen = cbInBuffer; Byte encodedProps[LZMA_PROPS_SIZE]; size_t encodedPropsSize = LZMA_PROPS_SIZE; SRes nResult; // Keep compilers happy STORMLIB_UNUSED(pCmpType); STORMLIB_UNUSED(nCmpLevel); // Fill the callbacks in structures Progress.Progress = LZMA_Callback_Progress; SzAlloc.Alloc = LZMA_Callback_Alloc; SzAlloc.Free = LZMA_Callback_Free; // Initialize properties LzmaEncProps_Init(&props); // Perform compression destBuffer = (Byte *)pvOutBuffer + LZMA_HEADER_SIZE; destLen = *pcbOutBuffer - LZMA_HEADER_SIZE; nResult = LzmaEncode(destBuffer, &destLen, (Byte *)pvInBuffer, srcLen, &props, encodedProps, &encodedPropsSize, 0, &Progress, &SzAlloc, &SzAlloc); if(nResult != SZ_OK) return; // If we failed to compress the data if(destLen >= (SizeT)(*pcbOutBuffer - LZMA_HEADER_SIZE)) return; // Write "useFilter" variable. Blizzard MPQ must not use filter. *pbOutBuffer++ = 0; // Copy the encoded properties to the output buffer memcpy(pvOutBuffer, encodedProps, encodedPropsSize); pbOutBuffer += encodedPropsSize; // Copy the size of the data *pbOutBuffer++ = (unsigned char)(srcLen >> 0x00); *pbOutBuffer++ = (unsigned char)(srcLen >> 0x08); *pbOutBuffer++ = (unsigned char)(srcLen >> 0x10); *pbOutBuffer++ = (unsigned char)(srcLen >> 0x18); *pbOutBuffer++ = 0; *pbOutBuffer++ = 0; *pbOutBuffer++ = 0; *pbOutBuffer++ = 0; // Give the size of the data to the caller *pcbOutBuffer = (unsigned int)(destLen + LZMA_HEADER_SIZE); } static int Decompress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { ELzmaStatus LzmaStatus; ISzAlloc SzAlloc; Byte * destBuffer = (Byte *)pvOutBuffer; Byte * srcBuffer = (Byte *)pvInBuffer; SizeT destLen = *pcbOutBuffer; SizeT srcLen = cbInBuffer; SRes nResult; // There must be at least 0x0E bytes in the buffer if(srcLen <= LZMA_HEADER_SIZE) return 0; // We only accept blocks that have no filter used if(*srcBuffer != 0) return 0; // Fill the callbacks in structures SzAlloc.Alloc = LZMA_Callback_Alloc; SzAlloc.Free = LZMA_Callback_Free; // Perform compression srcLen = cbInBuffer - LZMA_HEADER_SIZE; nResult = LzmaDecode(destBuffer, &destLen, srcBuffer + LZMA_HEADER_SIZE, &srcLen, srcBuffer + 1, LZMA_PROPS_SIZE, LZMA_FINISH_END, &LzmaStatus, &SzAlloc); if(nResult != SZ_OK) return 0; *pcbOutBuffer = (unsigned int)destLen; return 1; } static int Decompress_LZMA_MPK(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { ELzmaStatus LzmaStatus; ISzAlloc SzAlloc; Byte * destBuffer = (Byte *)pvOutBuffer; Byte * srcBuffer = (Byte *)pvInBuffer; SizeT destLen = *pcbOutBuffer; SizeT srcLen = cbInBuffer; SRes nResult; BYTE LZMA_Props[] = {0x5D, 0x00, 0x00, 0x00, 0x01}; // There must be at least 0x0E bytes in the buffer if(srcLen <= sizeof(LZMA_Props)) return 0; // Verify the props header if(memcmp(pvInBuffer, LZMA_Props, sizeof(LZMA_Props))) return 0; // Fill the callbacks in structures SzAlloc.Alloc = LZMA_Callback_Alloc; SzAlloc.Free = LZMA_Callback_Free; // Perform compression srcLen = cbInBuffer - sizeof(LZMA_Props); nResult = LzmaDecode(destBuffer, &destLen, srcBuffer + sizeof(LZMA_Props), &srcLen, srcBuffer, sizeof(LZMA_Props), LZMA_FINISH_END, &LzmaStatus, &SzAlloc); if(nResult != SZ_OK) return 0; *pcbOutBuffer = (unsigned int)destLen; return 1; } /******************************************************************************/ /* */ /* Support functions for SPARSE compression (0x20) */ /* */ /******************************************************************************/ void Compress_SPARSE(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { // Keep compilers happy STORMLIB_UNUSED(pCmpType); STORMLIB_UNUSED(nCmpLevel); CompressSparse(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer); } int Decompress_SPARSE(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { return DecompressSparse(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer); } /******************************************************************************/ /* */ /* Support for ADPCM mono compression (0x40) */ /* */ /******************************************************************************/ static void Compress_ADPCM_mono(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { // Prepare the compression level for Huffmann compression, // which will be called as next step if(0 < nCmpLevel && nCmpLevel <= 2) { nCmpLevel = 4; *pCmpType = 6; } else if(nCmpLevel == 3) { nCmpLevel = 6; *pCmpType = 8; } else { nCmpLevel = 5; *pCmpType = 7; } *pcbOutBuffer = CompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 1, nCmpLevel); } static int Decompress_ADPCM_mono(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { *pcbOutBuffer = DecompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 1); return 1; } /******************************************************************************/ /* */ /* Support for ADPCM stereo compression (0x80) */ /* */ /******************************************************************************/ static void Compress_ADPCM_stereo(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { // Prepare the compression level for Huffmann compression, // which will be called as next step if(0 < nCmpLevel && nCmpLevel <= 2) { nCmpLevel = 4; *pCmpType = 6; } else if(nCmpLevel == 3) { nCmpLevel = 6; *pCmpType = 8; } else { nCmpLevel = 5; *pCmpType = 7; } *pcbOutBuffer = CompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 2, nCmpLevel); } static int Decompress_ADPCM_stereo(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { *pcbOutBuffer = DecompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 2); return 1; } /*****************************************************************************/ /* */ /* SCompImplode */ /* */ /*****************************************************************************/ int WINAPI SCompImplode(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { int cbOutBuffer; // Check for valid parameters if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } // Perform the compression cbOutBuffer = *pcbOutBuffer; Compress_PKLIB(pvOutBuffer, &cbOutBuffer, pvInBuffer, cbInBuffer, NULL, 0); // If the compression was unsuccessful, copy the data as-is if(cbOutBuffer >= *pcbOutBuffer) { memcpy(pvOutBuffer, pvInBuffer, cbInBuffer); cbOutBuffer = *pcbOutBuffer; } *pcbOutBuffer = cbOutBuffer; return 1; } /*****************************************************************************/ /* */ /* SCompExplode */ /* */ /*****************************************************************************/ int WINAPI SCompExplode(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { int cbOutBuffer; // Check for valid parameters if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } // If the input length is the same as output length, do nothing. cbOutBuffer = *pcbOutBuffer; if(cbInBuffer == cbOutBuffer) { // If the buffers are equal, don't copy anything. if(pvInBuffer == pvOutBuffer) return 1; memcpy(pvOutBuffer, pvInBuffer, cbInBuffer); return 1; } // Perform decompression if(!Decompress_PKLIB(pvOutBuffer, &cbOutBuffer, pvInBuffer, cbInBuffer)) { SetLastError(ERROR_FILE_CORRUPT); return 0; } *pcbOutBuffer = cbOutBuffer; return 1; } /*****************************************************************************/ /* */ /* SCompCompress */ /* */ /*****************************************************************************/ // This table contains compress functions which can be applied to // uncompressed data. Each bit means the corresponding // compression method/function must be applied. // // WAVes compression Data compression // ------------------ ------------------- // 1st sector - 0x08 0x08 (D, HF, W2, SC, D2) // Next sectors - 0x81 0x02 (W3) static TCompressTable cmp_table[] = { {MPQ_COMPRESSION_SPARSE, Compress_SPARSE}, // Sparse compression {MPQ_COMPRESSION_ADPCM_MONO, Compress_ADPCM_mono}, // IMA ADPCM mono compression {MPQ_COMPRESSION_ADPCM_STEREO, Compress_ADPCM_stereo}, // IMA ADPCM stereo compression {MPQ_COMPRESSION_HUFFMANN, Compress_huff}, // Huffmann compression {MPQ_COMPRESSION_ZLIB, Compress_ZLIB}, // Compression with the "zlib" library {MPQ_COMPRESSION_PKWARE, Compress_PKLIB}, // Compression with Pkware DCL {MPQ_COMPRESSION_BZIP2, Compress_BZIP2} // Compression Bzip2 library }; int WINAPI SCompCompress(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, unsigned uCompressionMask, int nCmpType, int nCmpLevel) { COMPRESS CompressFuncArray[0x10]; // Array of compression functions, applied sequentially unsigned char CompressByte[0x10]; // CompressByte for each method in the CompressFuncArray array unsigned char * pbWorkBuffer = NULL; // Temporary storage for decompressed data unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer; unsigned char * pbOutput = (unsigned char *)pvOutBuffer;// Current output buffer unsigned char * pbInput = (unsigned char *)pvInBuffer; // Current input buffer int nCompressCount = 0; int nCompressIndex = 0; int nAtLeastOneCompressionDone = 0; int cbOutBuffer = 0; int cbInLength = cbInBuffer; int nResult = 1; // Check for valid parameters if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } // Zero input length brings zero output length if(cbInBuffer == 0) { *pcbOutBuffer = 0; return true; } // Setup the compression function array if(uCompressionMask == MPQ_COMPRESSION_LZMA) { CompressFuncArray[0] = Compress_LZMA; CompressByte[0] = (char)uCompressionMask; nCompressCount = 1; } else { // Fill the compressions array for(size_t i = 0; i < (sizeof(cmp_table) / sizeof(TCompressTable)); i++) { // If the mask agrees, insert the compression function to the array if(uCompressionMask & cmp_table[i].uMask) { CompressFuncArray[nCompressCount] = cmp_table[i].Compress; CompressByte[nCompressCount] = (unsigned char)cmp_table[i].uMask; uCompressionMask &= ~cmp_table[i].uMask; nCompressCount++; } } // If at least one of the compressions remaing unknown, return an error if(uCompressionMask != 0) { SetLastError(ERROR_NOT_SUPPORTED); return 0; } } // If there is at least one compression, do it if(nCompressCount > 0) { // If we need to do more than 1 compression, allocate intermediate buffer if(nCompressCount > 1) { pbWorkBuffer = STORM_ALLOC(unsigned char, *pcbOutBuffer); if(pbWorkBuffer == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } } // Get the current compression index nCompressIndex = nCompressCount - 1; // Perform all compressions in the array for(int i = 0; i < nCompressCount; i++) { // Choose the proper output buffer pbOutput = (nCompressIndex & 1) ? pbWorkBuffer : pbOutBuffer; nCompressIndex--; // Perform the (next) compression // Note that if the compression method is unable to compress the input data block // by at least 2 bytes, we consider it as failure and will use source data instead cbOutBuffer = *pcbOutBuffer - 1; CompressFuncArray[i](pbOutput + 1, &cbOutBuffer, pbInput, cbInLength, &nCmpType, nCmpLevel); // If the compression failed, we copy the input buffer as-is. // Note that there is one extra byte at the end of the intermediate buffer, so it should be OK if(cbOutBuffer > (cbInLength - 2)) { memcpy(pbOutput + nAtLeastOneCompressionDone, pbInput, cbInLength); cbOutBuffer = cbInLength; } else { // Remember that we have done at least one compression nAtLeastOneCompressionDone = 1; uCompressionMask |= CompressByte[i]; } // Now point input buffer to the output buffer pbInput = pbOutput + nAtLeastOneCompressionDone; cbInLength = cbOutBuffer; } // If at least one compression succeeded, put the compression // mask to the begin of the output buffer if(nAtLeastOneCompressionDone) *pbOutBuffer = (unsigned char)uCompressionMask; *pcbOutBuffer = cbOutBuffer + nAtLeastOneCompressionDone; } else { memcpy(pvOutBuffer, pvInBuffer, cbInBuffer); *pcbOutBuffer = cbInBuffer; } // Cleanup and return if(pbWorkBuffer != NULL) STORM_FREE(pbWorkBuffer); return nResult; } /*****************************************************************************/ /* */ /* SCompDecompress */ /* */ /*****************************************************************************/ // This table contains decompress functions which can be applied to // uncompressed data. The compression mask is stored in the first byte // of compressed data static TDecompressTable dcmp_table[] = { {MPQ_COMPRESSION_BZIP2, Decompress_BZIP2}, // Decompression with Bzip2 library {MPQ_COMPRESSION_PKWARE, Decompress_PKLIB}, // Decompression with Pkware Data Compression Library {MPQ_COMPRESSION_ZLIB, Decompress_ZLIB}, // Decompression with the "zlib" library {MPQ_COMPRESSION_HUFFMANN, Decompress_huff}, // Huffmann decompression {MPQ_COMPRESSION_ADPCM_STEREO, Decompress_ADPCM_stereo}, // IMA ADPCM stereo decompression {MPQ_COMPRESSION_ADPCM_MONO, Decompress_ADPCM_mono}, // IMA ADPCM mono decompression {MPQ_COMPRESSION_SPARSE, Decompress_SPARSE} // Sparse decompression }; int WINAPI SCompDecompress(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { unsigned char * pbWorkBuffer = NULL; unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer; unsigned char * pbInBuffer = (unsigned char *)pvInBuffer; unsigned char * pbOutput = (unsigned char *)pvOutBuffer; unsigned char * pbInput; unsigned uCompressionMask; // Decompressions applied to the data unsigned uCompressionCopy; // Decompressions applied to the data int cbOutBuffer = *pcbOutBuffer; // Current size of the output buffer int cbInLength; // Current size of the input buffer int nCompressCount = 0; // Number of compressions to be applied int nCompressIndex = 0; int nResult = 1; // Verify buffer sizes if(cbOutBuffer < cbInBuffer || cbInBuffer < 1) return 0; // If the input length is the same as output length, do nothing. if(cbOutBuffer == cbInBuffer) { // If the buffers are equal, don't copy anything. if(pvInBuffer != pvOutBuffer) memcpy(pvOutBuffer, pvInBuffer, cbInBuffer); return 1; } // Get applied compression types and decrement data length uCompressionMask = uCompressionCopy = (unsigned char)*pbInBuffer++; cbInBuffer--; // Get current compressed data and length of it pbInput = pbInBuffer; cbInLength = cbInBuffer; // This compression function doesn't support LZMA assert(uCompressionMask != MPQ_COMPRESSION_LZMA); // Parse the compression mask for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++) { // If the mask agrees, insert the compression function to the array if(uCompressionMask & dcmp_table[i].uMask) { uCompressionCopy &= ~dcmp_table[i].uMask; nCompressCount++; } } // If at least one of the compressions remaing unknown, return an error if(nCompressCount == 0 || uCompressionCopy != 0) { SetLastError(ERROR_NOT_SUPPORTED); return 0; } // If there is more than one compression, we have to allocate extra buffer if(nCompressCount > 1) { pbWorkBuffer = STORM_ALLOC(unsigned char, cbOutBuffer); if(pbWorkBuffer == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } } // Get the current compression index nCompressIndex = nCompressCount - 1; // Apply all decompressions for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++) { // Perform the (next) decompression if(uCompressionMask & dcmp_table[i].uMask) { // Get the correct output buffer pbOutput = (nCompressIndex & 1) ? pbWorkBuffer : pbOutBuffer; nCompressIndex--; // Perform the decompression cbOutBuffer = *pcbOutBuffer; nResult = dcmp_table[i].Decompress(pbOutput, &cbOutBuffer, pbInput, cbInLength); if(nResult == 0 || cbOutBuffer == 0) { SetLastError(ERROR_FILE_CORRUPT); nResult = 0; break; } // Switch buffers cbInLength = cbOutBuffer; pbInput = pbOutput; } } // Put the length of the decompressed data to the output buffer *pcbOutBuffer = cbOutBuffer; // Cleanup and return if(pbWorkBuffer != NULL) STORM_FREE(pbWorkBuffer); return nResult; } int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { DECOMPRESS pfnDecompress1 = NULL; DECOMPRESS pfnDecompress2 = NULL; unsigned char * pbWorkBuffer = (unsigned char *)pvOutBuffer; unsigned char * pbInBuffer = (unsigned char *)pvInBuffer; int cbWorkBuffer = *pcbOutBuffer; int nResult; char CompressionMethod; // Verify buffer sizes if(*pcbOutBuffer < cbInBuffer || cbInBuffer < 1) return 0; // If the outputbuffer is as big as input buffer, just copy the block if(*pcbOutBuffer == cbInBuffer) { if(pvOutBuffer != pvInBuffer) memcpy(pvOutBuffer, pvInBuffer, cbInBuffer); return 1; } // Get the compression methods CompressionMethod = *pbInBuffer++; cbInBuffer--; // We only recognize a fixed set of compression methods switch((unsigned char)CompressionMethod) { case MPQ_COMPRESSION_ZLIB: pfnDecompress1 = Decompress_ZLIB; break; case MPQ_COMPRESSION_PKWARE: pfnDecompress1 = Decompress_PKLIB; break; case MPQ_COMPRESSION_BZIP2: pfnDecompress1 = Decompress_BZIP2; break; case MPQ_COMPRESSION_LZMA: pfnDecompress1 = Decompress_LZMA; break; case MPQ_COMPRESSION_SPARSE: pfnDecompress1 = Decompress_SPARSE; break; case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_ZLIB): pfnDecompress1 = Decompress_ZLIB; pfnDecompress2 = Decompress_SPARSE; break; case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_BZIP2): pfnDecompress1 = Decompress_BZIP2; pfnDecompress2 = Decompress_SPARSE; break; // // Note: Any combination including MPQ_COMPRESSION_ADPCM_MONO, // MPQ_COMPRESSION_ADPCM_STEREO or MPQ_COMPRESSION_HUFFMANN // is not supported by newer MPQs. // case (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN): pfnDecompress1 = Decompress_huff; pfnDecompress2 = Decompress_ADPCM_mono; break; case (MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN): pfnDecompress1 = Decompress_huff; pfnDecompress2 = Decompress_ADPCM_stereo; break; default: SetLastError(ERROR_FILE_CORRUPT); return 0; } // If we have to use two decompressions, allocate temporary buffer if(pfnDecompress2 != NULL) { pbWorkBuffer = STORM_ALLOC(unsigned char, *pcbOutBuffer); if(pbWorkBuffer == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } } // Apply the first decompression method nResult = pfnDecompress1(pbWorkBuffer, &cbWorkBuffer, pbInBuffer, cbInBuffer); // Apply the second decompression method, if any if(pfnDecompress2 != NULL && nResult != 0) { cbInBuffer = cbWorkBuffer; cbWorkBuffer = *pcbOutBuffer; nResult = pfnDecompress2(pvOutBuffer, &cbWorkBuffer, pbWorkBuffer, cbInBuffer); } // Supply the output buffer size *pcbOutBuffer = cbWorkBuffer; // Free temporary buffer if(pbWorkBuffer != pvOutBuffer) STORM_FREE(pbWorkBuffer); if(nResult == 0) SetLastError(ERROR_FILE_CORRUPT); return nResult; } /*****************************************************************************/ /* */ /* File decompression for MPK archives */ /* */ /*****************************************************************************/ int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) { return Decompress_LZMA_MPK(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer); }