From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- .../openmpt-trunk/soundlib/ContainerMMCMP.cpp | 391 +++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 Src/external_dependencies/openmpt-trunk/soundlib/ContainerMMCMP.cpp (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/ContainerMMCMP.cpp') diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/ContainerMMCMP.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/ContainerMMCMP.cpp new file mode 100644 index 00000000..0f98338e --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/ContainerMMCMP.cpp @@ -0,0 +1,391 @@ +/* + * 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 &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 &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 >(); + 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(file.GetLength()), uint32_max / 20u) * 20u)); + // 8-bit deltas + uint8 ptable[256] = { 0 }; + + std::vector 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(blk.flags)), static_cast(blk.sub_blk))); + MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" pksize={} unpksize={}")(static_cast(blk.pk_size), static_cast(blk.unpk_size))); + MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" tt_entries={} num_bits={}")(static_cast(blk.tt_entries), static_cast(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(psubblk->position), static_cast(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(newval >> 8); + pDest[dwPos + 1] = static_cast(newval & 0xFF); + } else + { + pDest[dwPos + 0] = static_cast(newval & 0xFF); + pDest[dwPos + 1] = static_cast(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(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::as_span(unpackedData))); + + return true; +} + + +#endif // !MPT_WITH_ANCIENT + + +OPENMPT_NAMESPACE_END -- cgit