Line data Source code
1 : /* Copyright (C) 2020 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : #include "precompiled.h"
24 : #include "lib/file/archive/codec_zlib.h"
25 :
26 : #include "lib/alignment.h"
27 : #include "lib/file/archive/codec.h"
28 : #include "lib/external_libraries/zlib.h"
29 :
30 : #include "lib/sysdep/cpu.h"
31 :
32 : #include <cstring>
33 :
34 0 : class Codec_ZLib : public ICodec
35 : {
36 : public:
37 0 : u32 UpdateChecksum(u32 checksum, const u8* in, size_t inSize) const
38 : {
39 : #if CODEC_COMPUTE_CHECKSUM
40 0 : return (u32)crc32(checksum, in, (uInt)inSize);
41 : #else
42 : UNUSED2(checksum);
43 : UNUSED2(in);
44 : UNUSED2(inSize);
45 : return 0;
46 : #endif
47 : }
48 :
49 : protected:
50 0 : u32 InitializeChecksum()
51 : {
52 : #if CODEC_COMPUTE_CHECKSUM
53 0 : return crc32(0, 0, 0);
54 : #else
55 : return 0;
56 : #endif
57 : }
58 : };
59 :
60 :
61 : //-----------------------------------------------------------------------------
62 :
63 : class Codec_ZLibNone : public Codec_ZLib
64 : {
65 : public:
66 0 : Codec_ZLibNone()
67 0 : {
68 0 : Reset();
69 0 : }
70 :
71 0 : virtual ~Codec_ZLibNone()
72 0 : {
73 0 : }
74 :
75 0 : virtual size_t MaxOutputSize(size_t inSize) const
76 : {
77 0 : return inSize;
78 : }
79 :
80 0 : virtual Status Reset()
81 : {
82 0 : m_checksum = InitializeChecksum();
83 0 : return INFO::OK;
84 : }
85 :
86 0 : virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced)
87 : {
88 0 : const size_t transferSize = std::min(inSize, outSize);
89 0 : memcpy(out, in, transferSize);
90 0 : inConsumed = outProduced = transferSize;
91 0 : m_checksum = UpdateChecksum(m_checksum, out, outProduced);
92 0 : return INFO::OK;
93 : }
94 :
95 0 : virtual Status Finish(u32& checksum, size_t& outProduced)
96 : {
97 0 : outProduced = 0;
98 0 : checksum = m_checksum;
99 0 : return INFO::OK;
100 : }
101 :
102 : private:
103 : u32 m_checksum;
104 : };
105 :
106 :
107 : //-----------------------------------------------------------------------------
108 :
109 0 : class CodecZLibStream : public Codec_ZLib
110 : {
111 : protected:
112 0 : CodecZLibStream()
113 0 : {
114 0 : memset(&m_zs, 0, sizeof(m_zs));
115 0 : m_checksum = InitializeChecksum();
116 0 : }
117 :
118 0 : static Status LibError_from_zlib(int zlib_ret)
119 : {
120 0 : switch(zlib_ret)
121 : {
122 0 : case Z_OK:
123 0 : return INFO::OK;
124 0 : case Z_STREAM_END:
125 0 : WARN_RETURN(ERR::FAIL);
126 0 : case Z_MEM_ERROR:
127 0 : WARN_RETURN(ERR::NO_MEM);
128 0 : case Z_DATA_ERROR:
129 0 : WARN_RETURN(ERR::CORRUPTED);
130 0 : case Z_STREAM_ERROR:
131 0 : WARN_RETURN(ERR::INVALID_PARAM);
132 0 : default:
133 0 : WARN_RETURN(ERR::FAIL);
134 : }
135 : }
136 :
137 0 : static void WarnIfZLibError(int zlib_ret)
138 : {
139 0 : (void)LibError_from_zlib(zlib_ret);
140 0 : }
141 :
142 : typedef int ZEXPORT (*ZLibFunc)(z_streamp strm, int flush);
143 :
144 0 : Status CallStreamFunc(ZLibFunc func, int flush, const u8* in, const size_t inSize, u8* out, const size_t outSize, size_t& inConsumed, size_t& outProduced)
145 : {
146 0 : m_zs.next_in = (Byte*)in;
147 0 : m_zs.avail_in = (uInt)inSize;
148 0 : m_zs.next_out = (Byte*)out;
149 0 : m_zs.avail_out = (uInt)outSize;
150 :
151 0 : int ret = func(&m_zs, flush);
152 : // sanity check: if ZLib reports end of stream, all input data
153 : // must have been consumed.
154 0 : if(ret == Z_STREAM_END)
155 : {
156 0 : ENSURE(m_zs.avail_in == 0);
157 0 : ret = Z_OK;
158 : }
159 :
160 0 : ENSURE(inSize >= m_zs.avail_in && outSize >= m_zs.avail_out);
161 0 : inConsumed = inSize - m_zs.avail_in;
162 0 : outProduced = outSize - m_zs.avail_out;
163 :
164 0 : return LibError_from_zlib(ret);
165 : }
166 :
167 : mutable z_stream m_zs;
168 :
169 : // note: z_stream does contain an 'adler' checksum field, but that's
170 : // not updated in streams lacking a gzip header, so we'll have to
171 : // calculate a checksum ourselves.
172 : // adler32 is somewhat weaker than CRC32, but a more important argument
173 : // is that we should use the latter for compatibility with Zip archives.
174 : mutable u32 m_checksum;
175 : };
176 :
177 :
178 : //-----------------------------------------------------------------------------
179 :
180 : class Compressor_ZLib : public CodecZLibStream
181 : {
182 : public:
183 0 : Compressor_ZLib()
184 0 : {
185 : // note: with Z_BEST_COMPRESSION, 78% percent of
186 : // archive builder CPU time is spent in ZLib, even though
187 : // that is interleaved with IO; everything else is negligible.
188 : // we prefer faster speed at the cost of 1.5% larger archives.
189 0 : const int level = Z_BEST_SPEED;
190 0 : const int windowBits = -MAX_WBITS; // max window size; omit ZLib header
191 0 : const int memLevel = 9; // max speed; total mem ~= 384KiB
192 0 : const int strategy = Z_DEFAULT_STRATEGY; // normal data - not RLE
193 0 : const int ret = deflateInit2(&m_zs, level, Z_DEFLATED, windowBits, memLevel, strategy);
194 0 : ENSURE(ret == Z_OK);
195 0 : }
196 :
197 0 : virtual ~Compressor_ZLib()
198 0 : {
199 0 : const int ret = deflateEnd(&m_zs);
200 0 : WarnIfZLibError(ret);
201 0 : }
202 :
203 0 : virtual size_t MaxOutputSize(size_t inSize) const
204 : {
205 0 : return (size_t)deflateBound(&m_zs, (uLong)inSize);
206 : }
207 :
208 0 : virtual Status Reset()
209 : {
210 0 : m_checksum = InitializeChecksum();
211 0 : const int ret = deflateReset(&m_zs);
212 0 : return LibError_from_zlib(ret);
213 : }
214 :
215 0 : virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced)
216 : {
217 0 : m_checksum = UpdateChecksum(m_checksum, in, inSize);
218 0 : return CodecZLibStream::CallStreamFunc(deflate, 0, in, inSize, out, outSize, inConsumed, outProduced);
219 : }
220 :
221 0 : virtual Status Finish(u32& checksum, size_t& outProduced)
222 : {
223 0 : const uInt availOut = m_zs.avail_out;
224 :
225 : // notify zlib that no more data is forthcoming and have it flush output.
226 : // our output buffer has enough space due to use of deflateBound;
227 : // therefore, deflate must return Z_STREAM_END.
228 0 : const int ret = deflate(&m_zs, Z_FINISH);
229 0 : ENSURE(ret == Z_STREAM_END);
230 :
231 0 : outProduced = size_t(availOut - m_zs.avail_out);
232 :
233 0 : checksum = m_checksum;
234 0 : return INFO::OK;
235 : }
236 : };
237 :
238 :
239 : //-----------------------------------------------------------------------------
240 :
241 : class Decompressor_ZLib : public CodecZLibStream
242 : {
243 : public:
244 0 : Decompressor_ZLib()
245 0 : {
246 0 : const int windowBits = -MAX_WBITS; // max window size; omit ZLib header
247 0 : const int ret = inflateInit2(&m_zs, windowBits);
248 0 : ENSURE(ret == Z_OK);
249 0 : }
250 :
251 0 : virtual ~Decompressor_ZLib()
252 0 : {
253 0 : const int ret = inflateEnd(&m_zs);
254 0 : WarnIfZLibError(ret);
255 0 : }
256 :
257 0 : virtual size_t MaxOutputSize(size_t inSize) const
258 : {
259 : // relying on an upper bound for the output is a really bad idea for
260 : // large files. archive formats store the uncompressed file sizes,
261 : // so callers should use that when allocating the output buffer.
262 0 : ENSURE(inSize < 1*MiB);
263 :
264 0 : return inSize*1032; // see http://www.zlib.org/zlib_tech.html
265 : }
266 :
267 0 : virtual Status Reset()
268 : {
269 0 : m_checksum = InitializeChecksum();
270 0 : const int ret = inflateReset(&m_zs);
271 0 : return LibError_from_zlib(ret);
272 : }
273 :
274 0 : virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced)
275 : {
276 0 : const Status ret = CodecZLibStream::CallStreamFunc(inflate, Z_SYNC_FLUSH, in, inSize, out, outSize, inConsumed, outProduced);
277 0 : m_checksum = UpdateChecksum(m_checksum, out, outProduced);
278 0 : return ret;
279 : }
280 :
281 0 : virtual Status Finish(u32& checksum, size_t& outProduced)
282 : {
283 : // no action needed - decompression always flushes immediately.
284 0 : outProduced = 0;
285 :
286 0 : checksum = m_checksum;
287 0 : return INFO::OK;
288 : }
289 : };
290 :
291 :
292 : //-----------------------------------------------------------------------------
293 :
294 0 : PICodec CreateCodec_ZLibNone()
295 : {
296 0 : return PICodec(new Codec_ZLibNone);
297 : }
298 :
299 0 : PICodec CreateCompressor_ZLibDeflate()
300 : {
301 0 : return PICodec(new Compressor_ZLib);
302 : }
303 :
304 0 : PICodec CreateDecompressor_ZLibDeflate()
305 : {
306 0 : return PICodec (new Decompressor_ZLib);
307 3 : }
|