1 /*
2  * ContainerMMCMP.cpp
3  * ------------------
4  * Purpose: Handling of MMCMP 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 #include "BitReader.h"
18 
19 
20 OPENMPT_NAMESPACE_BEGIN
21 
22 
23 #if !defined(MPT_WITH_ANCIENT)
24 
25 
26 #ifdef MPT_ALL_LOGGING
27 #define MMCMP_LOG
28 #endif
29 
30 
31 struct MMCMPFileHeader
32 {
33 	char     id[8];    // "ziRCONia"
34 	uint16le hdrsize;  // size of all the remaining header data
35 	uint16le version;
36 	uint16le nblocks;
37 	uint32le filesize;
38 	uint32le blktable;
39 	uint8le  glb_comp;
40 	uint8le  fmt_comp;
41 
ValidateMMCMPFileHeader42 	bool Validate() const
43 	{
44 		if(std::memcmp(id, "ziRCONia", 8) != 0)
45 			return false;
46 		if(hdrsize != 14)
47 			return false;
48 		if(nblocks == 0)
49 			return false;
50 		if(filesize == 0)
51 			return false;
52 		if(filesize >= 0x80000000)
53 			return false;
54 		if(blktable < sizeof(MMCMPFileHeader))
55 			return false;
56 		return true;
57 	}
58 };
59 
60 MPT_BINARY_STRUCT(MMCMPFileHeader, 24)
61 
62 struct MMCMPBlock
63 {
64 	uint32le unpk_size;
65 	uint32le pk_size;
66 	uint32le xor_chk;
67 	uint16le sub_blk;
68 	uint16le flags;
69 	uint16le tt_entries;
70 	uint16le num_bits;
71 };
72 
73 MPT_BINARY_STRUCT(MMCMPBlock, 20)
74 
75 struct MMCMPSubBlock
76 {
77 	uint32le position;
78 	uint32le size;
79 
ValidateMMCMPSubBlock80 	bool Validate(std::vector<char> &unpackedData, const uint32 unpackedSize) const
81 	{
82 		if(position >= unpackedSize)
83 			return false;
84 		if(size > unpackedSize)
85 			return false;
86 		if(size > unpackedSize - position)
87 			return false;
88 		if(size == 0)
89 			return false;
90 		if(unpackedData.size() < position + size)
91 			unpackedData.resize(position + size);
92 		return true;
93 	}
94 };
95 
96 MPT_BINARY_STRUCT(MMCMPSubBlock, 8)
97 
98 enum MMCMPFlags : uint16
99 {
100 	MMCMP_COMP   = 0x0001,
101 	MMCMP_DELTA  = 0x0002,
102 	MMCMP_16BIT  = 0x0004,
103 	MMCMP_STEREO = 0x0100,
104 	MMCMP_ABS16  = 0x0200,
105 	MMCMP_ENDIAN = 0x0400,
106 };
107 
108 static constexpr uint8 MMCMP8BitCommands[8] =
109 {
110 	0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8
111 };
112 
113 static constexpr uint8 MMCMP8BitFetch[8] =
114 {
115 	3, 3, 3, 3, 2, 1, 0, 0
116 };
117 
118 static constexpr uint16 MMCMP16BitCommands[16] =
119 {
120 	0x01,  0x03,  0x07,  0x0F,  0x1E,   0x3C,   0x78,   0xF0,
121 	0x1F0, 0x3F0, 0x7F0, 0xFF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0
122 };
123 
124 static constexpr uint8 MMCMP16BitFetch[16] =
125 {
126 	4, 4, 4, 4, 3, 2, 1, 0,
127 	0, 0, 0, 0, 0, 0, 0, 0
128 };
129 
130 
ProbeFileHeaderMMCMP(MemoryFileReader file,const uint64 * pfilesize)131 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMMCMP(MemoryFileReader file, const uint64 *pfilesize)
132 {
133 	MMCMPFileHeader mfh;
134 	if(!file.ReadStruct(mfh))
135 		return ProbeWantMoreData;
136 	if(!mfh.Validate())
137 		return ProbeFailure;
138 	MPT_UNREFERENCED_PARAMETER(pfilesize);
139 	return ProbeSuccess;
140 }
141 
142 
UnpackMMCMP(std::vector<ContainerItem> & containerItems,FileReader & file,ContainerLoadingFlags loadFlags)143 bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, ContainerLoadingFlags loadFlags)
144 {
145 	file.Rewind();
146 	containerItems.clear();
147 
148 	MMCMPFileHeader mfh;
149 	if(!file.ReadStruct(mfh))
150 		return false;
151 	if(!mfh.Validate())
152 		return false;
153 	if(loadFlags == ContainerOnlyVerifyHeader)
154 		return true;
155 	if(!file.LengthIsAtLeast(mfh.blktable))
156 		return false;
157 	if(!file.LengthIsAtLeast(mfh.blktable + 4 * mfh.nblocks))
158 		return false;
159 
160 	containerItems.emplace_back();
161 	containerItems.back().data_cache = std::make_unique<std::vector<char> >();
162 	auto &unpackedData = *(containerItems.back().data_cache);
163 
164 	// Generally it's not so simple to establish an upper limit for the uncompressed data size (blocks can be reused, etc.),
165 	// so we just reserve a realistic amount of memory.
166 	const uint32 unpackedSize = mfh.filesize;
167 	unpackedData.reserve(std::min(unpackedSize, std::min(mpt::saturate_cast<uint32>(file.GetLength()), uint32_max / 20u) * 20u));
168 	// 8-bit deltas
169 	uint8 ptable[256] = { 0 };
170 
171 	std::vector<MMCMPSubBlock> subblks;
172 	for(uint32 nBlock = 0; nBlock < mfh.nblocks; nBlock++)
173 	{
174 		if(!file.Seek(mfh.blktable + 4 * nBlock))
175 			return false;
176 		if(!file.CanRead(4))
177 			return false;
178 		uint32 blkPos = file.ReadUint32LE();
179 		if(!file.Seek(blkPos))
180 			return false;
181 		MMCMPBlock blk;
182 		if(!file.ReadStruct(blk))
183 			return false;
184 		if(!file.ReadVector(subblks, blk.sub_blk))
185 			return false;
186 		const MMCMPSubBlock *psubblk = blk.sub_blk > 0 ? subblks.data() : nullptr;
187 
188 		if(blkPos + sizeof(MMCMPBlock) + blk.sub_blk * sizeof(MMCMPSubBlock) >= file.GetLength())
189 			return false;
190 		uint32 memPos = blkPos + sizeof(MMCMPBlock) + blk.sub_blk * sizeof(MMCMPSubBlock);
191 
192 #ifdef MMCMP_LOG
193 		MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT("block {}: flags={} sub_blocks={}")(nBlock, mpt::ufmt::HEX0<4>(static_cast<uint16>(blk.flags)), static_cast<uint16>(blk.sub_blk)));
194 		MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" pksize={} unpksize={}")(static_cast<uint32>(blk.pk_size), static_cast<uint32>(blk.unpk_size)));
195 		MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" tt_entries={} num_bits={}")(static_cast<uint16>(blk.tt_entries), static_cast<uint16>(blk.num_bits)));
196 #endif
197 		if(!(blk.flags & MMCMP_COMP))
198 		{
199 			// Data is not packed
200 			for(uint32 i = 0; i < blk.sub_blk; i++)
201 			{
202 				if(!psubblk)
203 					return false;
204 				if(!psubblk->Validate(unpackedData, unpackedSize))
205 					return false;
206 #ifdef MMCMP_LOG
207 				MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT("  Unpacked sub-block {}: offset {}, size={}")(i, static_cast<uint32>(psubblk->position), static_cast<uint32>(psubblk->size)));
208 #endif
209 				if(!file.Seek(memPos))
210 					return false;
211 				if(file.ReadRaw(mpt::span(&(unpackedData[psubblk->position]), psubblk->size)).size() != psubblk->size)
212 					return false;
213 				psubblk++;
214 			}
215 		} else if(blk.flags & MMCMP_16BIT)
216 		{
217 			// Data is 16-bit packed
218 			uint32 subblk = 0;
219 			if(!psubblk)
220 				return false;
221 			if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
222 				return false;
223 			char *pDest = &(unpackedData[psubblk[subblk].position]);
224 			uint32 dwSize = psubblk[subblk].size & ~1u;
225 			if(!dwSize)
226 				return false;
227 			uint32 dwPos = 0;
228 			uint32 numbits = blk.num_bits;
229 			uint32 oldval = 0;
230 
231 #ifdef MMCMP_LOG
232 			MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT("  16-bit block: pos={} size={} {} {}")(psubblk->position, psubblk->size, (blk.flags & MMCMP_DELTA) ? U_("DELTA ") : U_(""), (blk.flags & MMCMP_ABS16) ? U_("ABS16 ") : U_("")));
233 #endif
234 			if(!file.Seek(memPos + blk.tt_entries)) return false;
235 			if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false;
236 			BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) };
237 
238 			try
239 			{
240 				while (subblk < blk.sub_blk)
241 				{
242 					uint32 newval = 0x10000;
243 					uint32 d = bitFile.ReadBits(numbits + 1);
244 
245 					uint32 command = MMCMP16BitCommands[numbits & 0x0F];
246 					if(d >= command)
247 					{
248 						uint32 nFetch = MMCMP16BitFetch[numbits & 0x0F];
249 						uint32 newbits = bitFile.ReadBits(nFetch) + ((d - command) << nFetch);
250 						if(newbits != numbits)
251 						{
252 							numbits = newbits & 0x0F;
253 						} else if((d = bitFile.ReadBits(4)) == 0x0F)
254 						{
255 							if(bitFile.ReadBits(1))
256 								break;
257 							newval = 0xFFFF;
258 						} else
259 						{
260 							newval = 0xFFF0 + d;
261 						}
262 					} else
263 					{
264 						newval = d;
265 					}
266 					if(newval < 0x10000)
267 					{
268 						newval = (newval & 1) ? (uint32)(-(int32)((newval + 1) >> 1)) : (uint32)(newval >> 1);
269 						if(blk.flags & MMCMP_DELTA)
270 						{
271 							newval += oldval;
272 							oldval = newval;
273 						} else if(!(blk.flags & MMCMP_ABS16))
274 						{
275 							newval ^= 0x8000;
276 						}
277 						if(blk.flags & MMCMP_ENDIAN)
278 						{
279 								pDest[dwPos + 0] = static_cast<uint8>(newval >> 8);
280 								pDest[dwPos + 1] = static_cast<uint8>(newval & 0xFF);
281 						} else
282 						{
283 							pDest[dwPos + 0] = static_cast<uint8>(newval & 0xFF);
284 							pDest[dwPos + 1] = static_cast<uint8>(newval >> 8);
285 						}
286 						dwPos += 2;
287 					}
288 					if(dwPos >= dwSize)
289 					{
290 						subblk++;
291 						dwPos = 0;
292 						if(!(subblk < blk.sub_blk))
293 							break;
294 						if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
295 							return false;
296 						dwSize = psubblk[subblk].size & ~1u;
297 						if(!dwSize)
298 							return false;
299 						pDest = &(unpackedData[psubblk[subblk].position]);
300 					}
301 				}
302 			} catch(const BitReader::eof &)
303 			{
304 			}
305 		} else
306 		{
307 			// Data is 8-bit packed
308 			uint32 subblk = 0;
309 			if(!psubblk)
310 				return false;
311 			if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
312 				return false;
313 			char *pDest = &(unpackedData[psubblk[subblk].position]);
314 			uint32 dwSize = psubblk[subblk].size;
315 			uint32 dwPos = 0;
316 			uint32 numbits = blk.num_bits;
317 			uint32 oldval = 0;
318 			if(blk.tt_entries > sizeof(ptable)
319 				|| !file.Seek(memPos)
320 				|| file.ReadRaw(mpt::span(ptable, blk.tt_entries)).size() < blk.tt_entries)
321 				return false;
322 
323 			if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false;
324 			BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) };
325 
326 			try
327 			{
328 				while (subblk < blk.sub_blk)
329 				{
330 					uint32 newval = 0x100;
331 					uint32 d = bitFile.ReadBits(numbits + 1);
332 
333 					uint32 command = MMCMP8BitCommands[numbits & 0x07];
334 					if(d >= command)
335 					{
336 						uint32 nFetch = MMCMP8BitFetch[numbits & 0x07];
337 						uint32 newbits = bitFile.ReadBits(nFetch) + ((d - command) << nFetch);
338 						if(newbits != numbits)
339 						{
340 							numbits = newbits & 0x07;
341 						} else if((d = bitFile.ReadBits(3)) == 7)
342 						{
343 							if(bitFile.ReadBits(1))
344 								break;
345 							newval = 0xFF;
346 						} else
347 						{
348 							newval = 0xF8 + d;
349 						}
350 					} else
351 					{
352 						newval = d;
353 					}
354 					if(newval < sizeof(ptable))
355 					{
356 						int n = ptable[newval];
357 						if(blk.flags & MMCMP_DELTA)
358 						{
359 							n += oldval;
360 							oldval = n;
361 						}
362 						pDest[dwPos++] = static_cast<uint8>(n);
363 					}
364 					if(dwPos >= dwSize)
365 					{
366 						subblk++;
367 						dwPos = 0;
368 						if(!(subblk < blk.sub_blk))
369 							break;
370 						if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
371 							return false;
372 						dwSize = psubblk[subblk].size;
373 						pDest = &(unpackedData[psubblk[subblk].position]);
374 					}
375 				}
376 			} catch(const BitReader::eof &)
377 			{
378 			}
379 		}
380 	}
381 
382 	containerItems.back().file = FileReader(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(unpackedData)));
383 
384 	return true;
385 }
386 
387 
388 #endif // !MPT_WITH_ANCIENT
389 
390 
391 OPENMPT_NAMESPACE_END
392