1 /*
2  -----------------------------------------------------------------------------
3  This source file is part of OGRE
4  (Object-oriented Graphics Rendering Engine)
5  For the latest info, see http://www.ogre3d.org/
6 
7  Copyright (c) 2000-2013 Torus Knot Software Ltd
8 
9  Permission is hereby granted, free of charge, to any person obtaining a copy
10  of this software and associated documentation files (the "Software"), to deal
11  in the Software without restriction, including without limitation the rights
12  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13  copies of the Software, and to permit persons to whom the Software is
14  furnished to do so, subject to the following conditions:
15 
16  The above copyright notice and this permission notice shall be included in
17  all copies or substantial portions of the Software.
18 
19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25  THE SOFTWARE.
26  -----------------------------------------------------------------------------
27  */
28 #include "OgreStableHeaders.h"
29 
30 #if OGRE_NO_ZIP_ARCHIVE == 0
31 
32 #include "OgreDeflate.h"
33 #include "OgreException.h"
34 #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
35 #include "macUtils.h"
36 #endif
37 
38 #include <zlib.h>
39 
40 namespace Ogre
41 {
42 	// memory implementations
OgreZalloc(void * opaque,unsigned int items,unsigned int size)43 	void* OgreZalloc(void* opaque, unsigned int items, unsigned int size)
44 	{
45 		return OGRE_MALLOC(items * size, MEMCATEGORY_GENERAL);
46 	}
OgreZfree(void * opaque,void * address)47 	void OgreZfree(void* opaque, void* address)
48 	{
49 		OGRE_FREE(address, MEMCATEGORY_GENERAL);
50 	}
51 	#define OGRE_DEFLATE_TMP_SIZE 16384
52     //---------------------------------------------------------------------
DeflateStream(const DataStreamPtr & compressedStream,const String & tmpFileName,size_t avail_in)53 	DeflateStream::DeflateStream(const DataStreamPtr& compressedStream, const String& tmpFileName, size_t avail_in)
54 	: DataStream(compressedStream->getAccessMode())
55 	, mCompressedStream(compressedStream)
56     , mTempFileName(tmpFileName)
57 	, mZStream(0)
58 	, mCurrentPos(0)
59 	, mAvailIn(avail_in)
60 	, mTmp(0)
61 	, mIsCompressedValid(true)
62 	{
63 		init();
64 	}
65     //---------------------------------------------------------------------
DeflateStream(const String & name,const DataStreamPtr & compressedStream,const String & tmpFileName,size_t avail_in)66 	DeflateStream::DeflateStream(const String& name, const DataStreamPtr& compressedStream, const String& tmpFileName, size_t avail_in)
67 	: DataStream(name, compressedStream->getAccessMode())
68 	, mCompressedStream(compressedStream)
69     , mTempFileName(tmpFileName)
70 	, mZStream(0)
71 	, mCurrentPos(0)
72 	, mAvailIn(avail_in)
73 	, mTmp(0)
74 	, mIsCompressedValid(true)
75 	{
76 		init();
77 	}
78     //---------------------------------------------------------------------
getAvailInForSinglePass()79 	size_t DeflateStream::getAvailInForSinglePass()
80 	{
81 		size_t ret = OGRE_DEFLATE_TMP_SIZE;
82 
83 		// if we are doing particial-uncompressing
84 		if(mAvailIn>0)
85 		{
86 			if(mAvailIn<ret)
87 				ret = mAvailIn;
88 			mAvailIn -= ret;
89 		}
90 
91 		return ret;
92 	}
93     //---------------------------------------------------------------------
init()94 	void DeflateStream::init()
95 	{
96 		mZStream = OGRE_ALLOC_T(z_stream, 1, MEMCATEGORY_GENERAL);
97 		mZStream->zalloc = OgreZalloc;
98 		mZStream->zfree = OgreZfree;
99 
100 		if (getAccessMode() == READ)
101 		{
102 			mTmp = (unsigned char*)OGRE_MALLOC(OGRE_DEFLATE_TMP_SIZE, MEMCATEGORY_GENERAL);
103 			size_t restorePoint = mCompressedStream->tell();
104 			// read early chunk
105 			mZStream->next_in = mTmp;
106 			mZStream->avail_in = static_cast<uint>(mCompressedStream->read(mTmp, getAvailInForSinglePass()));
107 
108 			if (inflateInit(mZStream) != Z_OK)
109 			{
110 				mIsCompressedValid = false;
111 			}
112 			else
113 				mIsCompressedValid = true;
114 
115 			if (mIsCompressedValid)
116 			{
117 				// in fact, inflateInit on some implementations doesn't try to read
118 				// anything. We need to at least read something to test
119 				Bytef testOut[4];
120 				size_t savedIn = mZStream->avail_in;
121 				mZStream->avail_out = 4;
122 				mZStream->next_out = testOut;
123 				if (inflate(mZStream, Z_SYNC_FLUSH) != Z_OK)
124 					mIsCompressedValid = false;
125 				// restore for reading
126 				mZStream->avail_in = static_cast<uint>(savedIn);
127 				mZStream->next_in = mTmp;
128 
129 				inflateReset(mZStream);
130 			}
131 
132 			if (!mIsCompressedValid)
133 			{
134 				// Not compressed data!
135 				// Fail gracefully, fall back on reading the underlying stream direct
136 				destroy();
137 				mCompressedStream->seek(restorePoint);
138 			}
139 		}
140 		else
141 		{
142             if(mTempFileName.empty())
143             {
144                 // Write to temp file
145 #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
146                 char* tmpname = _tempnam(".", "ogre");
147                 if (!tmpname)
148                 {
149                     // Having no file name here will cause various problems later.
150                     OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Temporary file name generation failed.", "DeflateStream::init");
151                 }
152                 else
153                 {
154                     mTempFileName = tmpname;
155                     free(tmpname);
156                 }
157 #elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
158                 mTempFileName = macTempFileName();
159 #else
160                 char tmpname[L_tmpnam];
161                 if (!tmpnam(tmpname))
162                     OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Temporary file name generation failed.", "DeflateStream::init");
163 
164                 mTempFileName = tmpname;
165 #endif
166             }
167 
168 			std::fstream *f = OGRE_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
169 			f->open(mTempFileName.c_str(), std::ios::binary | std::ios::out);
170 			mTmpWriteStream = DataStreamPtr(OGRE_NEW FileStreamDataStream(f));
171 
172 		}
173 
174 	}
175     //---------------------------------------------------------------------
destroy()176 	void DeflateStream::destroy()
177 	{
178 		if (getAccessMode() == READ)
179 			inflateEnd(mZStream);
180 
181 		OGRE_FREE(mZStream, MEMCATEGORY_GENERAL);
182 		mZStream = 0;
183 		OGRE_FREE(mTmp, MEMCATEGORY_GENERAL);
184 		mTmp = 0;
185 	}
186 	//---------------------------------------------------------------------
~DeflateStream()187 	DeflateStream::~DeflateStream()
188 	{
189 		close();
190 		destroy();
191 	}
192     //---------------------------------------------------------------------
read(void * buf,size_t count)193 	size_t DeflateStream::read(void* buf, size_t count)
194 	{
195 		if (!mIsCompressedValid)
196 		{
197 			return mCompressedStream->read(buf, count);
198 		}
199 
200 		if (getAccessMode() & WRITE)
201 		{
202 			return mTmpWriteStream->read(buf, count);
203 		}
204 		else
205 		{
206 
207 			size_t restorePoint = mCompressedStream->tell();
208 			// read from cache first
209 			size_t cachereads = mReadCache.read(buf, count);
210 
211 			size_t newReadUncompressed = 0;
212 
213 			if (cachereads < count)
214 			{
215 				mZStream->avail_out = static_cast<uint>(count - cachereads);
216 				mZStream->next_out = (Bytef*)buf + cachereads;
217 
218 				while (mZStream->avail_out)
219 				{
220 					// Pull next chunk of compressed data from the underlying stream
221 					if (!mZStream->avail_in && !mCompressedStream->eof())
222 					{
223 						mZStream->avail_in = static_cast<uint>(mCompressedStream->read(mTmp, getAvailInForSinglePass()));
224 						mZStream->next_in = mTmp;
225 					}
226 
227 					if (mZStream->avail_in)
228 					{
229 						int availpre = mZStream->avail_out;
230 						int status = inflate(mZStream, Z_SYNC_FLUSH);
231 						size_t readUncompressed = availpre - mZStream->avail_out;
232 						newReadUncompressed += readUncompressed;
233 						if (status != Z_OK)
234 						{
235 							// End of data, or error
236 							if (status != Z_STREAM_END)
237 							{
238 								mCompressedStream->seek(restorePoint);
239 								OGRE_EXCEPT(Exception::ERR_INVALID_STATE,
240 											"Error in compressed stream",
241 											"DeflateStrea::read");
242 							}
243 							else
244 							{
245 								// back up the stream so that it can be used from the end onwards
246 								long unusedCompressed = mZStream->avail_in;
247 								mCompressedStream->skip(-unusedCompressed);
248 							}
249 
250 							break;
251 						}
252 					}
253 				}
254 			}
255 
256 			// Cache the last bytes read
257 			mReadCache.cacheData((char*)buf + cachereads, newReadUncompressed);
258 
259 			mCurrentPos += newReadUncompressed + cachereads;
260 
261 			return newReadUncompressed + cachereads;
262 		}
263 	}
264     //---------------------------------------------------------------------
write(const void * buf,size_t count)265 	size_t DeflateStream::write(const void* buf, size_t count)
266 	{
267 		if ((getAccessMode() & WRITE) == 0)
268 			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
269 						"Not a writable stream", "DeflateStream::write");
270 
271 		return mTmpWriteStream->write(buf, count);
272 	}
273     //---------------------------------------------------------------------
compressFinal()274 	void DeflateStream::compressFinal()
275 	{
276 		// Close temp stream
277 		mTmpWriteStream->close();
278 
279 		// Copy & compress
280 		// We do this rather than compress directly because some code seeks
281 		// around while writing (e.g. to update size blocks) which is not
282 		// possible when compressing on the fly
283 
284 		int ret, flush;
285 		char in[OGRE_DEFLATE_TMP_SIZE];
286 		char out[OGRE_DEFLATE_TMP_SIZE];
287 
288 		if (deflateInit(mZStream, Z_DEFAULT_COMPRESSION) != Z_OK)
289 		{
290 			destroy();
291 			OGRE_EXCEPT(Exception::ERR_INVALID_STATE,
292 						"Error initialising deflate compressed stream!",
293 						"DeflateStream::init");
294 		}
295 
296 		std::ifstream inFile;
297 		inFile.open(mTempFileName.c_str(), std::ios::in | std::ios::binary);
298 
299 		do
300 		{
301 			inFile.read(in, OGRE_DEFLATE_TMP_SIZE);
302 			mZStream->avail_in = (uInt)inFile.gcount();
303 			if (inFile.bad())
304 			{
305 				deflateEnd(mZStream);
306 				OGRE_EXCEPT(Exception::ERR_INVALID_STATE,
307 							"Error reading temp uncompressed stream!",
308 							"DeflateStream::init");
309 			}
310 			flush = inFile.eof() ? Z_FINISH : Z_NO_FLUSH;
311 			mZStream->next_in = (Bytef*)in;
312 
313 			/* run deflate() on input until output buffer not full, finish
314 			 compression if all of source has been read in */
315 			do
316 			{
317 				mZStream->avail_out = OGRE_DEFLATE_TMP_SIZE;
318 				mZStream->next_out = (Bytef*)out;
319 				ret = deflate(mZStream, flush);    /* no bad return value */
320 				assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
321 				size_t compressed = OGRE_DEFLATE_TMP_SIZE - mZStream->avail_out;
322 				mCompressedStream->write(out, compressed);
323 			} while (mZStream->avail_out == 0);
324 			assert(mZStream->avail_in == 0);     /* all input will be used */
325 
326 			/* done when last data in file processed */
327 		} while (flush != Z_FINISH);
328 		assert(ret == Z_STREAM_END);        /* stream will be complete */
329                 (void)ret;
330 		deflateEnd(mZStream);
331 
332         inFile.close();
333 		remove(mTempFileName.c_str());
334 
335 	}
336     //---------------------------------------------------------------------
skip(long count)337 	void DeflateStream::skip(long count)
338 	{
339 		if (!mIsCompressedValid)
340 		{
341 			mCompressedStream->skip(count);
342 			return;
343 		}
344 
345 		if (getAccessMode() & WRITE)
346 		{
347 			mTmpWriteStream->skip(count);
348 		}
349 		else
350 		{
351 			if (count > 0)
352 			{
353 				if (!mReadCache.ff(count))
354 				{
355 					OGRE_EXCEPT(Exception::ERR_INVALID_STATE,
356 								"You can only skip within the cache range in a deflate stream.",
357 								"DeflateStream::skip");
358 				}
359 			}
360 			else if (count < 0)
361 			{
362 				if (!mReadCache.rewind((size_t)(-count)))
363 				{
364 					OGRE_EXCEPT(Exception::ERR_INVALID_STATE,
365 								"You can only skip within the cache range in a deflate stream.",
366 								"DeflateStream::skip");
367 				}
368 			}
369 		}
370 		mCurrentPos = static_cast<size_t>(static_cast<long>(mCurrentPos) + count);
371 
372 
373 	}
374     //---------------------------------------------------------------------
seek(size_t pos)375 	void DeflateStream::seek( size_t pos )
376 	{
377 		if (!mIsCompressedValid)
378 		{
379 			mCompressedStream->seek(pos);
380 			return;
381 		}
382 		if (getAccessMode() & WRITE)
383 		{
384 			mTmpWriteStream->seek(pos);
385 		}
386 		else
387 		{
388 			if (pos == 0)
389 			{
390 				mCurrentPos = 0;
391 				mZStream->next_in = mTmp;
392 				mCompressedStream->seek(0);
393 				mZStream->avail_in = static_cast<uint>(mCompressedStream->read(mTmp, getAvailInForSinglePass()));
394 				inflateReset(mZStream);
395 			}
396 			else
397 			{
398 				skip(pos - tell());
399 			}
400 		}
401 	}
402     //---------------------------------------------------------------------
tell(void) const403 	size_t DeflateStream::tell(void) const
404 	{
405 		if (!mIsCompressedValid)
406 		{
407 			return mCompressedStream->tell();
408 		}
409 		else if(getAccessMode() & WRITE)
410 		{
411 			return mTmpWriteStream->tell();
412 		}
413 		else
414 		{
415 			return mCurrentPos;
416 		}
417 
418 	}
419     //---------------------------------------------------------------------
eof(void) const420 	bool DeflateStream::eof(void) const
421 	{
422 		if (getAccessMode() & WRITE)
423 			return mTmpWriteStream->eof();
424 		else
425 		{
426 			if (!mIsCompressedValid)
427 				return mCompressedStream->eof();
428 			else
429 				return mCompressedStream->eof() && mZStream->avail_in == 0;
430 		}
431 	}
432     //---------------------------------------------------------------------
close(void)433 	void DeflateStream::close(void)
434 	{
435 		if (getAccessMode() & WRITE)
436 		{
437 			compressFinal();
438 		}
439 
440 		// don't close underlying compressed stream in case used for something else
441 	}
442     //---------------------------------------------------------------------
443 
444 
445 }
446 
447 #endif
448