|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include <algorithm> |
|
6 #include "SeekableZStream.h" |
|
7 #include "Logging.h" |
|
8 |
|
9 bool |
|
10 SeekableZStream::Init(const void *buf, size_t length) |
|
11 { |
|
12 const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf); |
|
13 if (!header) { |
|
14 LOG("Not a seekable zstream"); |
|
15 return false; |
|
16 } |
|
17 |
|
18 buffer = reinterpret_cast<const unsigned char *>(buf); |
|
19 totalSize = header->totalSize; |
|
20 chunkSize = header->chunkSize; |
|
21 lastChunkSize = header->lastChunkSize; |
|
22 windowBits = header->windowBits; |
|
23 dictionary.Init(buffer + sizeof(SeekableZStreamHeader), header->dictSize); |
|
24 offsetTable.Init(buffer + sizeof(SeekableZStreamHeader) + header->dictSize, |
|
25 header->nChunks); |
|
26 filter = GetFilter(header->filter); |
|
27 |
|
28 /* Sanity check */ |
|
29 if ((chunkSize == 0) || |
|
30 (!IsPageAlignedSize(chunkSize)) || |
|
31 (chunkSize > 8 * PageSize()) || |
|
32 (offsetTable.numElements() < 1) || |
|
33 (lastChunkSize == 0) || |
|
34 (lastChunkSize > chunkSize) || |
|
35 (length < totalSize)) { |
|
36 LOG("Malformed or broken seekable zstream"); |
|
37 return false; |
|
38 } |
|
39 |
|
40 return true; |
|
41 } |
|
42 |
|
43 bool |
|
44 SeekableZStream::Decompress(void *where, size_t chunk, size_t length) |
|
45 { |
|
46 while (length) { |
|
47 size_t len = std::min(length, static_cast<size_t>(chunkSize)); |
|
48 if (!DecompressChunk(where, chunk, len)) |
|
49 return false; |
|
50 where = reinterpret_cast<unsigned char *>(where) + len; |
|
51 length -= len; |
|
52 chunk++; |
|
53 } |
|
54 return true; |
|
55 } |
|
56 |
|
57 bool |
|
58 SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length) |
|
59 { |
|
60 if (chunk >= offsetTable.numElements()) { |
|
61 LOG("DecompressChunk: chunk #%" PRIdSize " out of range [0-%" PRIdSize ")", |
|
62 chunk, offsetTable.numElements()); |
|
63 return false; |
|
64 } |
|
65 |
|
66 bool isLastChunk = (chunk == offsetTable.numElements() - 1); |
|
67 |
|
68 size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize; |
|
69 |
|
70 if (length == 0 || length > chunkLen) |
|
71 length = chunkLen; |
|
72 |
|
73 DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")", |
|
74 chunk, where, length, chunkLen); |
|
75 z_stream zStream; |
|
76 memset(&zStream, 0, sizeof(zStream)); |
|
77 zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1])) |
|
78 - uint32_t(offsetTable[chunk]); |
|
79 zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk])); |
|
80 zStream.avail_out = length; |
|
81 zStream.next_out = reinterpret_cast<Bytef *>(where); |
|
82 |
|
83 /* Decompress chunk */ |
|
84 if (inflateInit2(&zStream, windowBits) != Z_OK) { |
|
85 LOG("inflateInit failed: %s", zStream.msg); |
|
86 return false; |
|
87 } |
|
88 if (dictionary && inflateSetDictionary(&zStream, dictionary, |
|
89 dictionary.numElements()) != Z_OK) { |
|
90 LOG("inflateSetDictionary failed: %s", zStream.msg); |
|
91 return false; |
|
92 } |
|
93 if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH) |
|
94 != (length == chunkLen) ? Z_STREAM_END : Z_OK) { |
|
95 LOG("inflate failed: %s", zStream.msg); |
|
96 return false; |
|
97 } |
|
98 if (inflateEnd(&zStream) != Z_OK) { |
|
99 LOG("inflateEnd failed: %s", zStream.msg); |
|
100 return false; |
|
101 } |
|
102 if (filter) |
|
103 filter(chunk * chunkSize, UNFILTER, (unsigned char *)where, chunkLen); |
|
104 |
|
105 return true; |
|
106 } |
|
107 |
|
108 /* Branch/Call/Jump conversion filter for Thumb, derived from xz-utils |
|
109 * by Igor Pavlov and Lasse Collin, published in the public domain */ |
|
110 static void |
|
111 BCJ_Thumb_filter(off_t offset, SeekableZStream::FilterDirection dir, |
|
112 unsigned char *buf, size_t size) |
|
113 { |
|
114 size_t i; |
|
115 for (i = 0; i + 4 <= size; i += 2) { |
|
116 if ((buf[i + 1] & 0xf8) == 0xf0 && (buf[i + 3] & 0xf8) == 0xf8) { |
|
117 uint32_t src = (buf[i] << 11) |
|
118 | ((buf[i + 1] & 0x07) << 19) |
|
119 | buf[i + 2] |
|
120 | ((buf[i + 3] & 0x07) << 8); |
|
121 src <<= 1; |
|
122 uint32_t dest; |
|
123 if (dir == SeekableZStream::FILTER) |
|
124 dest = offset + (uint32_t)(i) + 4 + src; |
|
125 else |
|
126 dest = src - (offset + (uint32_t)(i) + 4); |
|
127 |
|
128 dest >>= 1; |
|
129 buf[i] = dest >> 11; |
|
130 buf[i + 1] = 0xf0 | ((dest >> 19) & 0x07); |
|
131 buf[i + 2] = dest; |
|
132 buf[i + 3] = 0xf8 | ((dest >> 8) & 0x07); |
|
133 i += 2; |
|
134 } |
|
135 } |
|
136 } |
|
137 |
|
138 /* Branch/Call/Jump conversion filter for ARM, derived from xz-utils |
|
139 * by Igor Pavlov and Lasse Collin, published in the public domain */ |
|
140 static void |
|
141 BCJ_ARM_filter(off_t offset, SeekableZStream::FilterDirection dir, |
|
142 unsigned char *buf, size_t size) |
|
143 { |
|
144 size_t i; |
|
145 for (i = 0; i + 4 <= size; i += 4) { |
|
146 if (buf[i + 3] == 0xeb) { |
|
147 uint32_t src = buf[i] |
|
148 | (buf[i + 1] << 8) |
|
149 | (buf[i + 2] << 16); |
|
150 src <<= 2; |
|
151 uint32_t dest; |
|
152 if (dir == SeekableZStream::FILTER) |
|
153 dest = offset + (uint32_t)(i) + 8 + src; |
|
154 else |
|
155 dest = src - (offset + (uint32_t)(i) + 8); |
|
156 |
|
157 dest >>= 2; |
|
158 buf[i] = dest; |
|
159 buf[i + 1] = dest >> 8; |
|
160 buf[i + 2] = dest >> 16; |
|
161 } |
|
162 } |
|
163 } |
|
164 |
|
165 /* Branch/Call/Jump conversion filter for x86, derived from xz-utils |
|
166 * by Igor Pavlov and Lasse Collin, published in the public domain */ |
|
167 |
|
168 #define Test86MSByte(b) ((b) == 0 || (b) == 0xff) |
|
169 |
|
170 static void |
|
171 BCJ_X86_filter(off_t offset, SeekableZStream::FilterDirection dir, |
|
172 unsigned char *buf, size_t size) |
|
173 { |
|
174 static const bool MASK_TO_ALLOWED_STATUS[8] = |
|
175 { true, true, true, false, true, false, false, false }; |
|
176 |
|
177 static const uint32_t MASK_TO_BIT_NUMBER[8] = |
|
178 { 0, 1, 2, 2, 3, 3, 3, 3 }; |
|
179 |
|
180 uint32_t prev_mask = 0; |
|
181 uint32_t prev_pos = 0; |
|
182 |
|
183 for (size_t i = 0; i + 5 <= size;) { |
|
184 uint8_t b = buf[i]; |
|
185 if (b != 0xe8 && b != 0xe9) { |
|
186 ++i; |
|
187 continue; |
|
188 } |
|
189 |
|
190 const uint32_t off = offset + (uint32_t)(i) - prev_pos; |
|
191 prev_pos = offset + (uint32_t)(i); |
|
192 |
|
193 if (off > 5) { |
|
194 prev_mask = 0; |
|
195 } else { |
|
196 for (uint32_t i = 0; i < off; ++i) { |
|
197 prev_mask &= 0x77; |
|
198 prev_mask <<= 1; |
|
199 } |
|
200 } |
|
201 |
|
202 b = buf[i + 4]; |
|
203 |
|
204 if (Test86MSByte(b) && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7] |
|
205 && (prev_mask >> 1) < 0x10) { |
|
206 |
|
207 uint32_t src = ((uint32_t)(b) << 24) |
|
208 | ((uint32_t)(buf[i + 3]) << 16) |
|
209 | ((uint32_t)(buf[i + 2]) << 8) |
|
210 | (buf[i + 1]); |
|
211 |
|
212 uint32_t dest; |
|
213 while (true) { |
|
214 if (dir == SeekableZStream::FILTER) |
|
215 dest = src + (offset + (uint32_t)(i) + 5); |
|
216 else |
|
217 dest = src - (offset + (uint32_t)(i) + 5); |
|
218 |
|
219 if (prev_mask == 0) |
|
220 break; |
|
221 |
|
222 const uint32_t i = MASK_TO_BIT_NUMBER[prev_mask >> 1]; |
|
223 |
|
224 b = (uint8_t)(dest >> (24 - i * 8)); |
|
225 |
|
226 if (!Test86MSByte(b)) |
|
227 break; |
|
228 |
|
229 src = dest ^ ((1 << (32 - i * 8)) - 1); |
|
230 } |
|
231 |
|
232 buf[i + 4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); |
|
233 buf[i + 3] = (uint8_t)(dest >> 16); |
|
234 buf[i + 2] = (uint8_t)(dest >> 8); |
|
235 buf[i + 1] = (uint8_t)(dest); |
|
236 i += 5; |
|
237 prev_mask = 0; |
|
238 |
|
239 } else { |
|
240 ++i; |
|
241 prev_mask |= 1; |
|
242 if (Test86MSByte(b)) |
|
243 prev_mask |= 0x10; |
|
244 } |
|
245 } |
|
246 } |
|
247 |
|
248 SeekableZStream::ZStreamFilter |
|
249 SeekableZStream::GetFilter(SeekableZStream::FilterId id) |
|
250 { |
|
251 switch (id) { |
|
252 case BCJ_THUMB: |
|
253 return BCJ_Thumb_filter; |
|
254 case BCJ_ARM: |
|
255 return BCJ_ARM_filter; |
|
256 case BCJ_X86: |
|
257 return BCJ_X86_filter; |
|
258 default: |
|
259 return nullptr; |
|
260 } |
|
261 return nullptr; |
|
262 } |