295 lines
7.2 KiB
C++
Vendored
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);
|
|
}
|
|
|
|
}
|