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