Files
winamp/Src/external_dependencies/openmpt-trunk/include/ancient/src/IMPDecompressor.cpp
2024-09-24 14:54:57 +02:00

295 lines
7.2 KiB
C++
Vendored

/* Copyright (C) Teemu Suutari */
#include "IMPDecompressor.hpp"
#include "HuffmanDecoder.hpp"
#include "InputStream.hpp"
#include "OutputStream.hpp"
#include "common/Common.hpp"
#include "common/OverflowCheck.hpp"
namespace ancient::internal
{
static bool readIMPHeader(uint32_t hdr,uint32_t &addition) noexcept
{
switch (hdr)
{
case FourCC("ATN!"):
case FourCC("EDAM"):
case FourCC("IMP!"):
case FourCC("M.H."):
addition=7;
return true;
case FourCC("BDPI"):
addition=0x6e8;
return true;
case FourCC("CHFI"):
addition=0xfe4;
return true;
case FourCC("RDC9"): // Files do not contain checksum
// I haven't got these files to be sure what is the addition
case FourCC("Dupa"):
case FourCC("FLT!"):
case FourCC("PARA"):
addition=0; // disable checksum for now
return true;
default:
return false;
}
}
bool IMPDecompressor::detectHeader(uint32_t hdr) noexcept
{
uint32_t dummy;
return readIMPHeader(hdr,dummy);
}
bool IMPDecompressor::detectHeaderXPK(uint32_t hdr) noexcept
{
return hdr==FourCC("IMPL");
}
std::shared_ptr<Decompressor> IMPDecompressor::create(const Buffer &packedData,bool exactSizeKnown,bool verify)
{
return std::make_shared<IMPDecompressor>(packedData,verify);
}
std::shared_ptr<XPKDecompressor> IMPDecompressor::create(uint32_t hdr,uint32_t recursionLevel,const Buffer &packedData,std::shared_ptr<XPKDecompressor::State> &state,bool verify)
{
return std::make_shared<IMPDecompressor>(hdr,recursionLevel,packedData,state,verify);
}
IMPDecompressor::IMPDecompressor(const Buffer &packedData,bool verify) :
_packedData(packedData)
{
uint32_t hdr=packedData.readBE32(0);
uint32_t checksumAddition;
if (!readIMPHeader(hdr,checksumAddition) || packedData.size()<0x32) throw InvalidFormatError();
_rawSize=packedData.readBE32(4);
_endOffset=packedData.readBE32(8);
if ((_endOffset&1) || _endOffset<0xc || _endOffset+0x32>packedData.size() ||
!_rawSize || !_endOffset ||
_rawSize>getMaxRawSize() || _endOffset>getMaxPackedSize()) throw InvalidFormatError();
uint32_t checksum=packedData.readBE32(_endOffset+0x2e);
if (verify && checksumAddition)
{
// size is divisible by 2
uint32_t sum=checksumAddition;
for (uint32_t i=0;i<_endOffset+0x2e;i+=2)
{
// TODO: slow, optimize
uint16_t tmp=_packedData.readBE16(i);
sum+=uint32_t(tmp);
}
if (checksum!=sum) throw InvalidFormatError();
}
}
IMPDecompressor::IMPDecompressor(uint32_t hdr,uint32_t recursionLevel,const Buffer &packedData,std::shared_ptr<XPKDecompressor::State> &state,bool verify) :
XPKDecompressor(recursionLevel),
_packedData(packedData)
{
if (!detectHeaderXPK(hdr) || packedData.size()<0x2e) throw InvalidFormatError();
_rawSize=packedData.readBE32(4);
_endOffset=packedData.readBE32(8);
if ((_endOffset&1) || _endOffset<0xc || OverflowCheck::sum(_endOffset,0x2eU)>packedData.size()) throw InvalidFormatError();
_isXPK=true;
}
IMPDecompressor::~IMPDecompressor()
{
// nothing needed
}
const std::string &IMPDecompressor::getName() const noexcept
{
static std::string name="IMP: File Imploder";
return name;
}
const std::string &IMPDecompressor::getSubName() const noexcept
{
static std::string name="XPK-IMPL: File Imploder";
return name;
}
size_t IMPDecompressor::getPackedSize() const noexcept
{
return _endOffset+0x32;
}
size_t IMPDecompressor::getRawSize() const noexcept
{
return _rawSize;
}
void IMPDecompressor::decompressImpl(Buffer &rawData,bool verify)
{
if (rawData.size()<_rawSize) throw DecompressionError();
class IMPInputStream
{
public:
IMPInputStream(const Buffer &buffer,size_t startOffset,size_t endOffset) :
_bufPtr(buffer.data()),
_currentOffset(endOffset),
_endOffset(startOffset),
_refOffset(endOffset)
{
if (_currentOffset<_endOffset || _currentOffset>buffer.size() || _endOffset>buffer.size()) throw Decompressor::DecompressionError();
uint8_t markerByte=buffer.read8(_currentOffset+16);
if (!(markerByte&0x80))
{
if (_currentOffset==_endOffset) throw Decompressor::DecompressionError();
_currentOffset--;
}
}
~IMPInputStream()
{
// nothing needed
}
uint8_t readByte()
{
// streamreader with funny ordering
auto sourceOffset=[&](size_t i)->size_t
{
if (i>=12)
{
return i;
} else {
if (i<4)
{
return i+_refOffset+8;
} else if (i<8) {
return i+_refOffset;
} else {
return i+_refOffset-8;
}
}
};
if (_currentOffset<=_endOffset) throw Decompressor::DecompressionError();
return _bufPtr[sourceOffset(--_currentOffset)];
}
bool eof() const { return _currentOffset==_endOffset; }
private:
const uint8_t *_bufPtr;
size_t _currentOffset;
size_t _endOffset;
size_t _refOffset;
};
IMPInputStream inputStream(_packedData,0,_endOffset);
MSBBitReader<IMPInputStream> bitReader(inputStream);
auto readBits=[&](uint32_t count)->uint32_t
{
return bitReader.readBits8(count);
};
auto readBit=[&]()->uint32_t
{
return bitReader.readBits8(1);
};
auto readByte=[&]()->uint8_t
{
return inputStream.readByte();
};
// the anchor-bit does not seem always to be at the correct place
{
uint8_t halfByte=_packedData.read8(_endOffset+17);
for (uint32_t i=0;i<7;i++)
if (halfByte&(1<<i))
{
bitReader.reset(halfByte>>(i+1),7-i);
break;
}
}
BackwardOutputStream outputStream(rawData,0,_rawSize);
// tables
uint16_t distanceValues[2][4];
for (uint32_t i=0;i<8;i++)
distanceValues[i>>2][i&3]=_packedData.readBE16(_endOffset+18+i*2);
uint8_t distanceBits[3][4];
for (uint32_t i=0;i<12;i++)
distanceBits[i>>2][i&3]=_packedData.read8(_endOffset+34+i);
// length, distance & literal counts are all intertwined
HuffmanDecoder<uint8_t> lldDecoder
{
HuffmanCode<uint8_t>{1,0b00000,0},
HuffmanCode<uint8_t>{2,0b00010,1},
HuffmanCode<uint8_t>{3,0b00110,2},
HuffmanCode<uint8_t>{4,0b01110,3},
HuffmanCode<uint8_t>{5,0b11110,4},
HuffmanCode<uint8_t>{5,0b11111,5}
};
HuffmanDecoder<uint8_t> lldDecoder2
{
HuffmanCode<uint8_t>{1,0b00,0},
HuffmanCode<uint8_t>{2,0b10,1},
HuffmanCode<uint8_t>{2,0b11,2}
};
// finally loop
uint32_t litLength=_packedData.readBE32(_endOffset+12);
for (;;)
{
for (uint32_t i=0;i<litLength;i++) outputStream.writeByte(readByte());
if (outputStream.eof()) break;
// now the intertwined Huffman table reads.
uint32_t i0=lldDecoder.decode(readBit);
uint32_t selector=(i0<4)?i0:3;
uint32_t count=i0+2;
if (count==6)
{
count+=readBits(3);
} else if (count==7) {
count=readByte();
// why this is error? (Well, it just is)
if (!count) throw DecompressionError();
}
static const uint8_t literalLengths[4]={6,10,10,18};
static const uint8_t literalBits[3][4]={
{1,1,1,1},
{2,3,3,4},
{4,5,7,14}};
uint32_t i1=lldDecoder2.decode(readBit);
litLength=i1+i1;
if (litLength==4)
{
litLength=literalLengths[selector];
}
litLength+=readBits(literalBits[i1][selector]);
uint32_t i2=lldDecoder2.decode(readBit);
uint32_t distance=1+((i2)?distanceValues[i2-1][selector]:0)+readBits(distanceBits[i2][selector]);
outputStream.copy(distance,count);
}
}
void IMPDecompressor::decompressImpl(Buffer &rawData,const Buffer &previousData,bool verify)
{
if (_rawSize!=rawData.size()) throw DecompressionError();
return decompressImpl(rawData,verify);
}
}