/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "plZlibStream.h" #include voidpf ZlibAlloc(voidpf opaque, uInt items, uInt size) { return ALLOC(items*size); } void ZlibFree(voidpf opaque, voidpf address) { FREE(address); } plZlibStream::plZlibStream() : fOutput(nil), fZStream(nil), fHeader(kNeedMoreData), fDecompressedOk(false) { } plZlibStream::~plZlibStream() { hsAssert(!fOutput && !fZStream, "plZlibStream not closed"); } hsBool plZlibStream::Open(const char* filename, const char* mode) { wchar* wFilename = hsStringToWString(filename); wchar* wMode = hsStringToWString(mode); hsBool ret = Open(wFilename, wMode); delete [] wFilename; delete [] wMode; return ret; } hsBool plZlibStream::Open(const wchar* filename, const wchar* mode) { fFilename = filename; fMode = mode; fOutput = NEW(hsUNIXStream); return fOutput->Open(filename, L"wb"); } hsBool plZlibStream::Close() { if (fOutput) { fOutput->Close(); DEL(fOutput); fOutput = nil; } if (fZStream) { z_streamp zstream = (z_streamp)fZStream; inflateEnd(zstream); DEL(zstream); fZStream = nil; } return true; } UInt32 plZlibStream::Write(UInt32 byteCount, const void* buffer) { UInt8* byteBuf = (UInt8*)buffer; // Check if we've read in the full gzip header yet if (fHeader == kNeedMoreData) { int cutAmt = IValidateGzHeader(byteCount, buffer); // Once we've read in the whole header, cut out whatever part of it is // in this buffer, leaving raw compressed data if (cutAmt != 0) { byteCount -= cutAmt; byteBuf += cutAmt; } } if (fHeader == kInvalidHeader) return 0; if (fHeader == kValidHeader) { ASSERT(fOutput); ASSERT(fZStream); z_streamp zstream = (z_streamp)fZStream; zstream->avail_in = byteCount; zstream->next_in = byteBuf; char outBuf[2048]; while (zstream->avail_in != 0) { zstream->avail_out = sizeof(outBuf); zstream->next_out = (UInt8*)outBuf; UInt32 amtWritten = zstream->total_out; int ret = inflate(zstream, Z_NO_FLUSH); bool inflateErr = (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_STREAM_ERROR || ret == Z_MEM_ERROR || ret == Z_BUF_ERROR); // If we have a decompression error, just fail if (inflateErr) { hsAssert(!inflateErr, "Error in inflate"); fHeader = kInvalidHeader; break; } amtWritten = zstream->total_out - amtWritten; fOutput->Write(amtWritten, outBuf); // If zlib says we hit the end of the stream, ignore avail_in if (ret == Z_STREAM_END) { fDecompressedOk = true; break; } } } return byteCount; } int plZlibStream::IValidateGzHeader(UInt32 byteCount, const void* buffer) { // Ripped from gzio.cpp #define HEAD_CRC 0x02 #define EXTRA_FIELD 0x04 #define ORIG_NAME 0x08 #define COMMENT 0x10 #define RESERVED 0xE0 static int gz_magic[2] = {0x1f, 0x8b}; // gzip magic header #define CheckForEnd() if (s.AtEnd()) { fHeader = kNeedMoreData; return 0; } int i; int initCacheSize = fHeaderCache.size(); for (i = 0; i < byteCount; i++) fHeaderCache.push_back(((UInt8*)buffer)[i]); hsReadOnlyStream s(fHeaderCache.size(), &fHeaderCache[0]); // Check the gzip magic header for (i = 0; i < 2; i++) { UInt8 c = s.ReadByte(); if (c != gz_magic[i]) { CheckForEnd(); fHeader = kInvalidHeader; return 0; } } int method = s.ReadByte(); int flags = s.ReadByte(); if (method != Z_DEFLATED || (flags & RESERVED) != 0) { CheckForEnd(); fHeader = kInvalidHeader; return 0; } // Discard time, xflags and OS code: for (i = 0; i < 6; i++) s.ReadByte(); CheckForEnd(); if ((flags & EXTRA_FIELD) != 0) { // skip the extra field UInt16 len = s.ReadSwap16(); while (len-- != 0 && s.ReadByte()) { CheckForEnd(); } } int c; // skip the original file name if ((flags & ORIG_NAME) != 0) { while ((c = s.ReadByte()) != 0) { CheckForEnd(); } } // skip the .gz file comment if ((flags & COMMENT) != 0) { while ((c = s.ReadByte()) != 0) { CheckForEnd(); } } // skip the header crc if ((flags & HEAD_CRC) != 0) { s.ReadSwap16(); CheckForEnd(); } CheckForEnd(); UInt32 headerSize = s.GetPosition(); UInt32 clipBuffer = headerSize - initCacheSize; // Initialize the zlib stream z_streamp zstream = NEW(z_stream_s); memset(zstream, 0, sizeof(z_stream_s)); zstream->zalloc = ZlibAlloc; zstream->zfree = ZlibFree; zstream->opaque = nil; zstream->avail_in = byteCount - clipBuffer; zstream->next_in = (UInt8*)&fHeaderCache[clipBuffer]; // Gotta use inflateInit2, because there's no header for zlib to look at. // The -15 tells it to not try and find a header bool initOk = (inflateInit2(zstream, -15) == Z_OK); fZStream = zstream; fHeaderCache.clear(); if (!initOk) { hsAssert(0, "Zip init failed"); fHeader = kInvalidHeader; return 0; } ASSERT(fZStream); fHeader = kValidHeader; return clipBuffer; } hsBool plZlibStream::AtEnd() { hsAssert(0, "AtEnd not supported"); return true; } UInt32 plZlibStream::Read(UInt32 byteCount, void* buffer) { hsAssert(0, "Read not supported"); return 0; } void plZlibStream::Skip(UInt32 deltaByteCount) { hsAssert(0, "Skip not supported"); } void plZlibStream::Rewind() { // hack so rewind will work (someone thought it would be funny to not implement base class functions) Close(); Open(fFilename.c_str(), fMode.c_str()); fHeader = kNeedMoreData; fDecompressedOk = false; } void plZlibStream::FastFwd() { hsAssert(0, "FastFwd not supported"); } UInt32 plZlibStream::GetEOF() { hsAssert(0, "GetEOF not supported"); return 0; }