/* 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 IMPDecompressor::create(const Buffer &packedData,bool exactSizeKnown,bool verify) { return std::make_shared(packedData,verify); } std::shared_ptr IMPDecompressor::create(uint32_t hdr,uint32_t recursionLevel,const Buffer &packedData,std::shared_ptr &state,bool verify) { return std::make_shared(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 &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 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+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 lldDecoder { HuffmanCode{1,0b00000,0}, HuffmanCode{2,0b00010,1}, HuffmanCode{3,0b00110,2}, HuffmanCode{4,0b01110,3}, HuffmanCode{5,0b11110,4}, HuffmanCode{5,0b11111,5} }; HuffmanDecoder lldDecoder2 { HuffmanCode{1,0b00,0}, HuffmanCode{2,0b10,1}, HuffmanCode{2,0b11,2} }; // finally loop uint32_t litLength=_packedData.readBE32(_endOffset+12); for (;;) { for (uint32_t i=0;i