1 /*
2 * ContainerXPK.cpp
3 * ----------------
4 * Purpose: Handling of XPK compressed modules
5 * Notes : (currently none)
6 * Authors: Olivier Lapicque
7 * OpenMPT Devs
8 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
9 */
10
11
12 #include "stdafx.h"
13
14 #include "../common/FileReader.h"
15 #include "Container.h"
16 #include "Sndfile.h"
17
18 #include <stdexcept>
19
20
21 OPENMPT_NAMESPACE_BEGIN
22
23
24 #if !defined(MPT_WITH_ANCIENT)
25
26
27 #ifdef MPT_ALL_LOGGING
28 #define MMCMP_LOG
29 #endif
30
31
32 struct XPKFILEHEADER
33 {
34 char XPKF[4];
35 uint32be SrcLen;
36 char SQSH[4];
37 uint32be DstLen;
38 char Name[16];
39 uint32be Reserved;
40 };
41
42 MPT_BINARY_STRUCT(XPKFILEHEADER, 36)
43
44
45 struct XPK_error : public std::range_error
46 {
XPK_errorXPK_error47 XPK_error() : std::range_error("invalid XPK data") { }
48 };
49
50 struct XPK_BufferBounds
51 {
52 const uint8 *pSrcBeg;
53 std::size_t SrcSize;
54
SrcReadXPK_BufferBounds55 inline uint8 SrcRead(std::size_t index)
56 {
57 if(index >= SrcSize) throw XPK_error();
58 return pSrcBeg[index];
59 }
60 };
61
bfextu(std::size_t p,int32 bo,int32 bc,XPK_BufferBounds & bufs)62 static int32 bfextu(std::size_t p, int32 bo, int32 bc, XPK_BufferBounds &bufs)
63 {
64 uint32 r;
65
66 p += bo / 8;
67 r = bufs.SrcRead(p); p++;
68 r <<= 8;
69 r |= bufs.SrcRead(p); p++;
70 r <<= 8;
71 r |= bufs.SrcRead(p);
72 r <<= bo % 8;
73 r &= 0xffffff;
74 r >>= 24 - bc;
75
76 return r;
77 }
78
bfexts(std::size_t p,int32 bo,int32 bc,XPK_BufferBounds & bufs)79 static int32 bfexts(std::size_t p, int32 bo, int32 bc, XPK_BufferBounds &bufs)
80 {
81 uint32 r;
82
83 p += bo / 8;
84 r = bufs.SrcRead(p); p++;
85 r <<= 8;
86 r |= bufs.SrcRead(p); p++;
87 r <<= 8;
88 r |= bufs.SrcRead(p);
89 r <<= (bo % 8) + 8;
90 return mpt::rshift_signed(static_cast<int32>(r), 32 - bc);
91 }
92
93
XPK_ReadTable(int32 index)94 static uint8 XPK_ReadTable(int32 index)
95 {
96 static constexpr uint8 xpk_table[] = {
97 2,3,4,5,6,7,8,0,3,2,4,5,6,7,8,0,4,3,5,2,6,7,8,0,5,4,6,2,3,7,8,0,6,5,7,2,3,4,8,0,7,6,8,2,3,4,5,0,8,7,6,2,3,4,5,0
98 };
99 if(index < 0) throw XPK_error();
100 if(static_cast<std::size_t>(index) >= std::size(xpk_table)) throw XPK_error();
101 return xpk_table[index];
102 }
103
XPK_DoUnpack(const uint8 * src_,uint32 srcLen,std::vector<char> & unpackedData,int32 len)104 static bool XPK_DoUnpack(const uint8 *src_, uint32 srcLen, std::vector<char> &unpackedData, int32 len)
105 {
106 if(len <= 0) return false;
107 int32 d0,d1,d2,d3,d4,d5,d6,a2,a5;
108 int32 cp, cup1, type;
109 std::size_t c;
110 std::size_t src;
111 std::size_t phist = 0;
112
113 unpackedData.reserve(std::min(static_cast<uint32>(len), std::min(srcLen, uint32_max / 20u) * 20u));
114
115 XPK_BufferBounds bufs;
116 bufs.pSrcBeg = src_;
117 bufs.SrcSize = srcLen;
118
119 src = 0;
120 c = src;
121 while(len > 0)
122 {
123 type = bufs.SrcRead(c+0);
124 cp = (bufs.SrcRead(c+4)<<8) | (bufs.SrcRead(c+5)); // packed
125 cup1 = (bufs.SrcRead(c+6)<<8) | (bufs.SrcRead(c+7)); // unpacked
126 //Log(" packed=%6d unpacked=%6d bytes left=%d dst=%08X(%d)\n", cp, cup1, len, dst, dst);
127 c += 8;
128 src = c+2;
129 if (type == 0)
130 {
131 // RAW chunk
132 if(cp < 0 || cp > len) throw XPK_error();
133 for(int32 i = 0; i < cp; ++i)
134 {
135 unpackedData.push_back(bufs.SrcRead(c + i));
136 }
137 c+=cp;
138 len -= cp;
139 continue;
140 }
141
142 if (type != 1)
143 {
144 #ifdef MMCMP_LOG
145 MPT_LOG_GLOBAL(LogDebug, "XPK", MPT_UFORMAT("Invalid XPK type! ({} bytes left)")(len));
146 #endif
147 break;
148 }
149 LimitMax(cup1, len);
150 len -= cup1;
151 cp = (cp + 3) & 0xfffc;
152 c += cp;
153
154 d0 = d1 = d2 = a2 = 0;
155 d3 = bufs.SrcRead(src); src++;
156 unpackedData.push_back(static_cast<char>(d3));
157 cup1--;
158
159 while (cup1 > 0)
160 {
161 if (d1 >= 8) goto l6dc;
162 if (bfextu(src,d0,1,bufs)) goto l75a;
163 d0 += 1;
164 d5 = 0;
165 d6 = 8;
166 goto l734;
167
168 l6dc:
169 if (bfextu(src,d0,1,bufs)) goto l726;
170 d0 += 1;
171 if (! bfextu(src,d0,1,bufs)) goto l75a;
172 d0 += 1;
173 if (bfextu(src,d0,1,bufs)) goto l6f6;
174 d6 = 2;
175 goto l708;
176
177 l6f6:
178 d0 += 1;
179 if (!bfextu(src,d0,1,bufs)) goto l706;
180 d6 = bfextu(src,d0,3,bufs);
181 d0 += 3;
182 goto l70a;
183
184 l706:
185 d6 = 3;
186 l708:
187 d0 += 1;
188 l70a:
189 d6 = XPK_ReadTable((8*a2) + d6 -17);
190 if (d6 != 8) goto l730;
191 l718:
192 if (d2 >= 20)
193 {
194 d5 = 1;
195 goto l732;
196 }
197 d5 = 0;
198 goto l734;
199
200 l726:
201 d0 += 1;
202 d6 = 8;
203 if (d6 == a2) goto l718;
204 d6 = a2;
205 l730:
206 d5 = 4;
207 l732:
208 d2 += 8;
209 l734:
210 while ((d5 >= 0) && (cup1 > 0))
211 {
212 d4 = bfexts(src,d0,d6,bufs);
213 d0 += d6;
214 d3 -= d4;
215 unpackedData.push_back(static_cast<char>(d3));
216 cup1--;
217 d5--;
218 }
219 if (d1 != 31) d1++;
220 a2 = d6;
221 l74c:
222 d6 = d2;
223 d6 >>= 3;
224 d2 -= d6;
225 }
226 }
227 return !unpackedData.empty();
228
229 l75a:
230 d0 += 1;
231 if (bfextu(src,d0,1,bufs)) goto l766;
232 d4 = 2;
233 goto l79e;
234
235 l766:
236 d0 += 1;
237 if (bfextu(src,d0,1,bufs)) goto l772;
238 d4 = 4;
239 goto l79e;
240
241 l772:
242 d0 += 1;
243 if (bfextu(src,d0,1,bufs)) goto l77e;
244 d4 = 6;
245 goto l79e;
246
247 l77e:
248 d0 += 1;
249 if (bfextu(src,d0,1,bufs)) goto l792;
250 d0 += 1;
251 d6 = bfextu(src,d0,3,bufs);
252 d0 += 3;
253 d6 += 8;
254 goto l7a8;
255
256 l792:
257 d0 += 1;
258 d6 = bfextu(src,d0,5,bufs);
259 d0 += 5;
260 d4 = 16;
261 goto l7a6;
262
263 l79e:
264 d0 += 1;
265 d6 = bfextu(src,d0,1,bufs);
266 d0 += 1;
267 l7a6:
268 d6 += d4;
269 l7a8:
270 if(bfextu(src, d0, 1, bufs))
271 {
272 d5 = 12;
273 a5 = -0x100;
274 } else
275 {
276 d0 += 1;
277 if(bfextu(src, d0, 1, bufs))
278 {
279 d5 = 14;
280 a5 = -0x1100;
281 } else
282 {
283 d5 = 8;
284 a5 = 0;
285 }
286 }
287
288 d0 += 1;
289 d4 = bfextu(src,d0,d5,bufs);
290 d0 += d5;
291 d6 -= 3;
292 if (d6 >= 0)
293 {
294 if (d6 > 0) d1 -= 1;
295 d1 -= 1;
296 if (d1 < 0) d1 = 0;
297 }
298 d6 += 2;
299 phist = unpackedData.size() + a5 - d4 - 1;
300 if(phist >= unpackedData.size())
301 throw XPK_error();
302
303 while ((d6 >= 0) && (cup1 > 0))
304 {
305 d3 = unpackedData[phist];
306 phist++;
307 unpackedData.push_back(static_cast<char>(d3));
308 cup1--;
309 d6--;
310 }
311 goto l74c;
312 }
313
314
ValidateHeader(const XPKFILEHEADER & header)315 static bool ValidateHeader(const XPKFILEHEADER &header)
316 {
317 if(std::memcmp(header.XPKF, "XPKF", 4) != 0)
318 {
319 return false;
320 }
321 if(std::memcmp(header.SQSH, "SQSH", 4) != 0)
322 {
323 return false;
324 }
325 if(header.SrcLen == 0)
326 {
327 return false;
328 }
329 if(header.DstLen == 0)
330 {
331 return false;
332 }
333 static_assert(sizeof(XPKFILEHEADER) >= 8);
334 if(header.SrcLen < (sizeof(XPKFILEHEADER) - 8))
335 {
336 return false;
337 }
338 return true;
339 }
340
341
ValidateHeaderFileSize(const XPKFILEHEADER & header,uint64 filesize)342 static bool ValidateHeaderFileSize(const XPKFILEHEADER &header, uint64 filesize)
343 {
344 if(filesize < header.SrcLen - 8)
345 {
346 return false;
347 }
348 return true;
349 }
350
351
ProbeFileHeaderXPK(MemoryFileReader file,const uint64 * pfilesize)352 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderXPK(MemoryFileReader file, const uint64 *pfilesize)
353 {
354 XPKFILEHEADER header;
355 if(!file.ReadStruct(header))
356 {
357 return ProbeWantMoreData;
358 }
359 if(!ValidateHeader(header))
360 {
361 return ProbeFailure;
362 }
363 if(pfilesize)
364 {
365 if(!ValidateHeaderFileSize(header, *pfilesize))
366 {
367 return ProbeFailure;
368 }
369 }
370 return ProbeSuccess;
371 }
372
373
UnpackXPK(std::vector<ContainerItem> & containerItems,FileReader & file,ContainerLoadingFlags loadFlags)374 bool UnpackXPK(std::vector<ContainerItem> &containerItems, FileReader &file, ContainerLoadingFlags loadFlags)
375 {
376 file.Rewind();
377 containerItems.clear();
378
379 XPKFILEHEADER header;
380 if(!file.ReadStruct(header))
381 {
382 return false;
383 }
384 if(!ValidateHeader(header))
385 {
386 return false;
387 }
388 if(loadFlags == ContainerOnlyVerifyHeader)
389 {
390 return true;
391 }
392
393 if(!file.CanRead(header.SrcLen - (sizeof(XPKFILEHEADER) - 8)))
394 {
395 return false;
396 }
397
398 containerItems.emplace_back();
399 containerItems.back().data_cache = std::make_unique<std::vector<char> >();
400 std::vector<char> & unpackedData = *(containerItems.back().data_cache);
401
402 #ifdef MMCMP_LOG
403 MPT_LOG_GLOBAL(LogDebug, "XPK", MPT_UFORMAT("XPK detected (SrcLen={} DstLen={}) filesize={}")(static_cast<uint32>(header.SrcLen), static_cast<uint32>(header.DstLen), file.GetLength()));
404 #endif
405 bool result = false;
406 try
407 {
408 result = XPK_DoUnpack(file.GetRawData<uint8>().data(), header.SrcLen - (sizeof(XPKFILEHEADER) - 8), unpackedData, header.DstLen);
409 } catch(mpt::out_of_memory e)
410 {
411 mpt::delete_out_of_memory(e);
412 return false;
413 } catch(const XPK_error &)
414 {
415 return false;
416 }
417
418 if(result)
419 {
420 containerItems.back().file = FileReader(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(unpackedData)));
421 }
422 return result;
423 }
424
425
426 #endif // !MPT_WITH_ANCIENT
427
428
429 OPENMPT_NAMESPACE_END
430