123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- /*
- * ContainerMMCMP.cpp
- * ------------------
- * Purpose: Handling of MMCMP compressed modules
- * Notes : (currently none)
- * Authors: Olivier Lapicque
- * OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "../common/FileReader.h"
- #include "Container.h"
- #include "Sndfile.h"
- #include "BitReader.h"
- OPENMPT_NAMESPACE_BEGIN
- #if !defined(MPT_WITH_ANCIENT)
- #ifdef MPT_ALL_LOGGING
- #define MMCMP_LOG
- #endif
- struct MMCMPFileHeader
- {
- char id[8]; // "ziRCONia"
- uint16le hdrsize; // size of all the remaining header data
- uint16le version;
- uint16le nblocks;
- uint32le filesize;
- uint32le blktable;
- uint8le glb_comp;
- uint8le fmt_comp;
- bool Validate() const
- {
- if(std::memcmp(id, "ziRCONia", 8) != 0)
- return false;
- if(hdrsize != 14)
- return false;
- if(nblocks == 0)
- return false;
- if(filesize == 0)
- return false;
- if(filesize >= 0x80000000)
- return false;
- if(blktable < sizeof(MMCMPFileHeader))
- return false;
- return true;
- }
- };
- MPT_BINARY_STRUCT(MMCMPFileHeader, 24)
- struct MMCMPBlock
- {
- uint32le unpk_size;
- uint32le pk_size;
- uint32le xor_chk;
- uint16le sub_blk;
- uint16le flags;
- uint16le tt_entries;
- uint16le num_bits;
- };
- MPT_BINARY_STRUCT(MMCMPBlock, 20)
- struct MMCMPSubBlock
- {
- uint32le position;
- uint32le size;
- bool Validate(std::vector<char> &unpackedData, const uint32 unpackedSize) const
- {
- if(position >= unpackedSize)
- return false;
- if(size > unpackedSize)
- return false;
- if(size > unpackedSize - position)
- return false;
- if(size == 0)
- return false;
- if(unpackedData.size() < position + size)
- unpackedData.resize(position + size);
- return true;
- }
- };
- MPT_BINARY_STRUCT(MMCMPSubBlock, 8)
- enum MMCMPFlags : uint16
- {
- MMCMP_COMP = 0x0001,
- MMCMP_DELTA = 0x0002,
- MMCMP_16BIT = 0x0004,
- MMCMP_STEREO = 0x0100,
- MMCMP_ABS16 = 0x0200,
- MMCMP_ENDIAN = 0x0400,
- };
- static constexpr uint8 MMCMP8BitCommands[8] =
- {
- 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8
- };
- static constexpr uint8 MMCMP8BitFetch[8] =
- {
- 3, 3, 3, 3, 2, 1, 0, 0
- };
- static constexpr uint16 MMCMP16BitCommands[16] =
- {
- 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF0,
- 0x1F0, 0x3F0, 0x7F0, 0xFF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0
- };
- static constexpr uint8 MMCMP16BitFetch[16] =
- {
- 4, 4, 4, 4, 3, 2, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0
- };
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMMCMP(MemoryFileReader file, const uint64 *pfilesize)
- {
- MMCMPFileHeader mfh;
- if(!file.ReadStruct(mfh))
- return ProbeWantMoreData;
- if(!mfh.Validate())
- return ProbeFailure;
- MPT_UNREFERENCED_PARAMETER(pfilesize);
- return ProbeSuccess;
- }
- bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, ContainerLoadingFlags loadFlags)
- {
- file.Rewind();
- containerItems.clear();
- MMCMPFileHeader mfh;
- if(!file.ReadStruct(mfh))
- return false;
- if(!mfh.Validate())
- return false;
- if(loadFlags == ContainerOnlyVerifyHeader)
- return true;
- if(!file.LengthIsAtLeast(mfh.blktable))
- return false;
- if(!file.LengthIsAtLeast(mfh.blktable + 4 * mfh.nblocks))
- return false;
- containerItems.emplace_back();
- containerItems.back().data_cache = std::make_unique<std::vector<char> >();
- auto &unpackedData = *(containerItems.back().data_cache);
- // Generally it's not so simple to establish an upper limit for the uncompressed data size (blocks can be reused, etc.),
- // so we just reserve a realistic amount of memory.
- const uint32 unpackedSize = mfh.filesize;
- unpackedData.reserve(std::min(unpackedSize, std::min(mpt::saturate_cast<uint32>(file.GetLength()), uint32_max / 20u) * 20u));
- // 8-bit deltas
- uint8 ptable[256] = { 0 };
- std::vector<MMCMPSubBlock> subblks;
- for(uint32 nBlock = 0; nBlock < mfh.nblocks; nBlock++)
- {
- if(!file.Seek(mfh.blktable + 4 * nBlock))
- return false;
- if(!file.CanRead(4))
- return false;
- uint32 blkPos = file.ReadUint32LE();
- if(!file.Seek(blkPos))
- return false;
- MMCMPBlock blk;
- if(!file.ReadStruct(blk))
- return false;
- if(!file.ReadVector(subblks, blk.sub_blk))
- return false;
- const MMCMPSubBlock *psubblk = blk.sub_blk > 0 ? subblks.data() : nullptr;
- if(blkPos + sizeof(MMCMPBlock) + blk.sub_blk * sizeof(MMCMPSubBlock) >= file.GetLength())
- return false;
- uint32 memPos = blkPos + sizeof(MMCMPBlock) + blk.sub_blk * sizeof(MMCMPSubBlock);
- #ifdef MMCMP_LOG
- 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)));
- MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" pksize={} unpksize={}")(static_cast<uint32>(blk.pk_size), static_cast<uint32>(blk.unpk_size)));
- MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" tt_entries={} num_bits={}")(static_cast<uint16>(blk.tt_entries), static_cast<uint16>(blk.num_bits)));
- #endif
- if(!(blk.flags & MMCMP_COMP))
- {
- // Data is not packed
- for(uint32 i = 0; i < blk.sub_blk; i++)
- {
- if(!psubblk)
- return false;
- if(!psubblk->Validate(unpackedData, unpackedSize))
- return false;
- #ifdef MMCMP_LOG
- MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" Unpacked sub-block {}: offset {}, size={}")(i, static_cast<uint32>(psubblk->position), static_cast<uint32>(psubblk->size)));
- #endif
- if(!file.Seek(memPos))
- return false;
- if(file.ReadRaw(mpt::span(&(unpackedData[psubblk->position]), psubblk->size)).size() != psubblk->size)
- return false;
- psubblk++;
- }
- } else if(blk.flags & MMCMP_16BIT)
- {
- // Data is 16-bit packed
- uint32 subblk = 0;
- if(!psubblk)
- return false;
- if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
- return false;
- char *pDest = &(unpackedData[psubblk[subblk].position]);
- uint32 dwSize = psubblk[subblk].size & ~1u;
- if(!dwSize)
- return false;
- uint32 dwPos = 0;
- uint32 numbits = blk.num_bits;
- uint32 oldval = 0;
- #ifdef MMCMP_LOG
- 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_("")));
- #endif
- if(!file.Seek(memPos + blk.tt_entries)) return false;
- if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false;
- BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) };
- try
- {
- while (subblk < blk.sub_blk)
- {
- uint32 newval = 0x10000;
- uint32 d = bitFile.ReadBits(numbits + 1);
- uint32 command = MMCMP16BitCommands[numbits & 0x0F];
- if(d >= command)
- {
- uint32 nFetch = MMCMP16BitFetch[numbits & 0x0F];
- uint32 newbits = bitFile.ReadBits(nFetch) + ((d - command) << nFetch);
- if(newbits != numbits)
- {
- numbits = newbits & 0x0F;
- } else if((d = bitFile.ReadBits(4)) == 0x0F)
- {
- if(bitFile.ReadBits(1))
- break;
- newval = 0xFFFF;
- } else
- {
- newval = 0xFFF0 + d;
- }
- } else
- {
- newval = d;
- }
- if(newval < 0x10000)
- {
- newval = (newval & 1) ? (uint32)(-(int32)((newval + 1) >> 1)) : (uint32)(newval >> 1);
- if(blk.flags & MMCMP_DELTA)
- {
- newval += oldval;
- oldval = newval;
- } else if(!(blk.flags & MMCMP_ABS16))
- {
- newval ^= 0x8000;
- }
- if(blk.flags & MMCMP_ENDIAN)
- {
- pDest[dwPos + 0] = static_cast<uint8>(newval >> 8);
- pDest[dwPos + 1] = static_cast<uint8>(newval & 0xFF);
- } else
- {
- pDest[dwPos + 0] = static_cast<uint8>(newval & 0xFF);
- pDest[dwPos + 1] = static_cast<uint8>(newval >> 8);
- }
- dwPos += 2;
- }
- if(dwPos >= dwSize)
- {
- subblk++;
- dwPos = 0;
- if(!(subblk < blk.sub_blk))
- break;
- if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
- return false;
- dwSize = psubblk[subblk].size & ~1u;
- if(!dwSize)
- return false;
- pDest = &(unpackedData[psubblk[subblk].position]);
- }
- }
- } catch(const BitReader::eof &)
- {
- }
- } else
- {
- // Data is 8-bit packed
- uint32 subblk = 0;
- if(!psubblk)
- return false;
- if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
- return false;
- char *pDest = &(unpackedData[psubblk[subblk].position]);
- uint32 dwSize = psubblk[subblk].size;
- uint32 dwPos = 0;
- uint32 numbits = blk.num_bits;
- uint32 oldval = 0;
- if(blk.tt_entries > sizeof(ptable)
- || !file.Seek(memPos)
- || file.ReadRaw(mpt::span(ptable, blk.tt_entries)).size() < blk.tt_entries)
- return false;
- if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false;
- BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) };
- try
- {
- while (subblk < blk.sub_blk)
- {
- uint32 newval = 0x100;
- uint32 d = bitFile.ReadBits(numbits + 1);
- uint32 command = MMCMP8BitCommands[numbits & 0x07];
- if(d >= command)
- {
- uint32 nFetch = MMCMP8BitFetch[numbits & 0x07];
- uint32 newbits = bitFile.ReadBits(nFetch) + ((d - command) << nFetch);
- if(newbits != numbits)
- {
- numbits = newbits & 0x07;
- } else if((d = bitFile.ReadBits(3)) == 7)
- {
- if(bitFile.ReadBits(1))
- break;
- newval = 0xFF;
- } else
- {
- newval = 0xF8 + d;
- }
- } else
- {
- newval = d;
- }
- if(newval < sizeof(ptable))
- {
- int n = ptable[newval];
- if(blk.flags & MMCMP_DELTA)
- {
- n += oldval;
- oldval = n;
- }
- pDest[dwPos++] = static_cast<uint8>(n);
- }
- if(dwPos >= dwSize)
- {
- subblk++;
- dwPos = 0;
- if(!(subblk < blk.sub_blk))
- break;
- if(!psubblk[subblk].Validate(unpackedData, unpackedSize))
- return false;
- dwSize = psubblk[subblk].size;
- pDest = &(unpackedData[psubblk[subblk].position]);
- }
- }
- } catch(const BitReader::eof &)
- {
- }
- }
- }
- containerItems.back().file = FileReader(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(unpackedData)));
- return true;
- }
- #endif // !MPT_WITH_ANCIENT
- OPENMPT_NAMESPACE_END
|