123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- /* Copyright (C) Teemu Suutari */
- #include <algorithm>
- #include "RNCDecompressor.hpp"
- #include "HuffmanDecoder.hpp"
- #include "InputStream.hpp"
- #include "OutputStream.hpp"
- #include "common/CRC16.hpp"
- #include "common/OverflowCheck.hpp"
- #include "common/Common.hpp"
- // This allows decompression of pc compressed files from unonfficial (and unpatched) compressor
- // PC games do not need chunk count, and are happy to read these files.
- // Official tools put it and amiga decompressors require it
- #define ALLOW_MISSING_CHUNKS 1
- namespace ancient::internal
- {
- bool RNCDecompressor::detectHeader(uint32_t hdr) noexcept
- {
- return hdr==FourCC("RNC\001") || hdr==FourCC("RNC\002");
- }
- std::shared_ptr<Decompressor> RNCDecompressor::create(const Buffer &packedData,bool exactSizeKnown,bool verify)
- {
- return std::make_shared<RNCDecompressor>(packedData,verify);
- }
- RNCDecompressor::RNCDecompressor(const Buffer &packedData,bool verify) :
- _packedData(packedData)
- {
- uint32_t hdr=packedData.readBE32(0);
- _rawSize=packedData.readBE32(4);
- _packedSize=packedData.readBE32(8);
- if (!_rawSize || !_packedSize ||
- _rawSize>getMaxRawSize() || _packedSize>getMaxPackedSize()) throw InvalidFormatError();
- bool verified=false;
- if (hdr==FourCC("RNC\001"))
- {
- // now detect between old and new version
- // since the old and the new version share the same id, there is no foolproof way
- // to tell them apart. It is easier to prove that it is not something by finding
- // specific invalid bitstream content.
- // well, this is silly though but lets assume someone has made old format RNC1 with total size less than 19
- if (packedData.size()<19)
- {
- _ver=Version::RNC1Old;
- } else {
- uint8_t newStreamStart=packedData.read8(18);
- uint8_t oldStreamStart=packedData.read8(_packedSize+11);
- // Check that stream starts with a literal(s)
- if (!(oldStreamStart&0x80))
- _ver=Version::RNC1New;
- // New stream have two bits in start as a filler on new stream. Those are always 0
- // (although this is not strictly mandated)
- // +
- // Even though it is possible to make new RNC1 stream which starts with zero literal table size,
- // it is extremely unlikely
- else if ((newStreamStart&3) || !(newStreamStart&0x7c))
- _ver=Version::RNC1Old;
- // now the last resort: check CRC.
- else if (_packedData.size()>=_packedSize+18 && CRC16(_packedData,18,_packedSize,0)==packedData.readBE16(14))
- {
- _ver=Version::RNC1New;
- verified=true;
- } else _ver=Version::RNC1Old;
- }
- } else if (hdr==FourCC("RNC\002")) {
- _ver=Version::RNC2;
- } else throw InvalidFormatError();
- uint32_t hdrSize=(_ver==Version::RNC1Old)?12:18;
- if (OverflowCheck::sum(_packedSize,hdrSize)>packedData.size()) throw InvalidFormatError();
- if (_ver!=Version::RNC1Old)
- {
- _rawCRC=packedData.readBE16(12);
- _chunks=packedData.read8(17);
- if (verify && !verified)
- {
- if (CRC16(_packedData,18,_packedSize,0)!=packedData.readBE16(14))
- throw VerificationError();
- }
- }
- }
- RNCDecompressor::~RNCDecompressor()
- {
- // nothing needed
- }
- const std::string &RNCDecompressor::getName() const noexcept
- {
- static std::string names[3]={
- "RNC1: Rob Northen RNC1 Compressor (old)",
- "RNC1: Rob Northen RNC1 Compressor ",
- "RNC2: Rob Northen RNC2 Compressor"};
- return names[static_cast<uint32_t>(_ver)];
- }
- size_t RNCDecompressor::getPackedSize() const noexcept
- {
- if (_ver==Version::RNC1Old) return _packedSize+12;
- else return _packedSize+18;
- }
- size_t RNCDecompressor::getRawSize() const noexcept
- {
- return _rawSize;
- }
- void RNCDecompressor::decompressImpl(Buffer &rawData,bool verify)
- {
- if (rawData.size()<_rawSize) throw DecompressionError();
- switch (_ver)
- {
- case Version::RNC1Old:
- return RNC1DecompressOld(rawData,verify);
- case Version::RNC1New:
- return RNC1DecompressNew(rawData,verify);
- case Version::RNC2:
- return RNC2Decompress(rawData,verify);
- default:
- throw DecompressionError();
- }
- }
- void RNCDecompressor::RNC1DecompressOld(Buffer &rawData,bool verify)
- {
- BackwardInputStream inputStream(_packedData,12,_packedSize+12);
- MSBBitReader<BackwardInputStream> 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=readByte();
- 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);
- HuffmanDecoder<uint8_t> litDecoder
- {
- HuffmanCode<uint8_t>{1,0b00,0},
- HuffmanCode<uint8_t>{2,0b10,1},
- HuffmanCode<uint8_t>{2,0b11,2}
- };
- HuffmanDecoder<uint8_t> lengthDecoder
- {
- HuffmanCode<uint8_t>{1,0b0000,0},
- HuffmanCode<uint8_t>{2,0b0010,1},
- HuffmanCode<uint8_t>{3,0b0110,2},
- HuffmanCode<uint8_t>{4,0b1110,3},
- HuffmanCode<uint8_t>{4,0b1111,4}
- };
- HuffmanDecoder<uint8_t> distanceDecoder
- {
- HuffmanCode<uint8_t>{1,0b00,0},
- HuffmanCode<uint8_t>{2,0b10,1},
- HuffmanCode<uint8_t>{2,0b11,2}
- };
- for (;;)
- {
- uint32_t litLength=litDecoder.decode(readBit);
- if (litLength==2)
- {
- static const uint32_t litBitLengths[4]={2,2,3,10};
- static const uint32_t litAdditions[4]={2,5,8,15};
- for (uint32_t i=0;i<4;i++)
- {
- litLength=readBits(litBitLengths[i]);
- if (litLength!=(1U<<litBitLengths[i])-1U || i==3)
- {
- litLength+=litAdditions[i];
- break;
- }
- }
- }
-
- for (uint32_t i=0;i<litLength;i++) outputStream.writeByte(readByte());
-
- // the only way to successfully end the loop!
- if (outputStream.eof()) break;
- uint32_t count;
- {
- uint32_t lengthIndex=lengthDecoder.decode(readBit);
- static const uint32_t lengthBitLengths[5]={0,0,1,2,10};
- static const uint32_t lengthAdditions[5]={2,3,4,6,10};
- count=readBits(lengthBitLengths[lengthIndex])+lengthAdditions[lengthIndex];
- }
- uint32_t distance;
- if (count!=2)
- {
- uint32_t distanceIndex=distanceDecoder.decode(readBit);
- static const uint32_t distanceBitLengths[3]={8,5,12};
- static const uint32_t distanceAdditions[3]={32,0,288};
- distance=readBits(distanceBitLengths[distanceIndex])+distanceAdditions[distanceIndex];
- } else {
- if (!readBit())
- {
- distance=readBits(6);
- } else {
- distance=readBits(9)+64;
- }
- }
- outputStream.copy((distance)?distance+count-1:1,count);
- }
- }
- void RNCDecompressor::RNC1DecompressNew(Buffer &rawData,bool verify)
- {
- ForwardInputStream inputStream(_packedData,18,_packedSize+18);
- LSBBitReader<ForwardInputStream> bitReader(inputStream);
- auto readBits=[&](uint32_t count)->uint32_t
- {
- return bitReader.readBits16Limit(count);
- };
- auto readByte=[&]()->uint8_t
- {
- return inputStream.readByte();
- };
- ForwardOutputStream outputStream(rawData,0,_rawSize);
- typedef HuffmanDecoder<uint32_t> RNC1HuffmanDecoder;
- // helpers
- auto readHuffmanTable=[&](RNC1HuffmanDecoder &dec)
- {
- uint32_t length=readBits(5);
- if (!length) return;
- uint32_t maxDepth=0;
- uint8_t lengthTable[31];
- for (uint32_t i=0;i<length;i++)
- {
- lengthTable[i]=readBits(4);
- if (lengthTable[i]>maxDepth) maxDepth=lengthTable[i];
- }
- dec.createOrderlyHuffmanTable(lengthTable,length);
- };
- auto huffmanDecode=[&](const RNC1HuffmanDecoder &dec)->int32_t
- {
- // this is kind of non-specced
- uint32_t ret=dec.decode([&]()->uint32_t{return readBits(1);});
- if (ret>=2)
- ret=(1<<(ret-1))|readBits(ret-1);
- return ret;
- };
- auto processLiterals=[&](const RNC1HuffmanDecoder &dec)
- {
- uint32_t litLength=huffmanDecode(dec);
- for (uint32_t i=0;i<litLength;i++) outputStream.writeByte(readByte());
- };
- readBits(2);
- #ifdef ALLOW_MISSING_CHUNKS
- while (!outputStream.eof())
- #else
- for (uint8_t chunks=0;chunks<_chunks;chunks++)
- #endif
- {
- RNC1HuffmanDecoder litDecoder,distanceDecoder,lengthDecoder;
- readHuffmanTable(litDecoder);
- readHuffmanTable(distanceDecoder);
- readHuffmanTable(lengthDecoder);
- uint32_t count=readBits(16);
- for (uint32_t sub=1;sub<count;sub++)
- {
- processLiterals(litDecoder);
- uint32_t distance=huffmanDecode(distanceDecoder);
- uint32_t count=huffmanDecode(lengthDecoder);
- distance++;
- count+=2;
- outputStream.copy(distance,count);
- }
- processLiterals(litDecoder);
- }
- if (!outputStream.eof()) throw DecompressionError();
- if (verify && CRC16(rawData,0,_rawSize,0)!=_rawCRC) throw VerificationError();
- }
- void RNCDecompressor::RNC2Decompress(Buffer &rawData,bool verify)
- {
- ForwardInputStream inputStream(_packedData,18,_packedSize+18);
- MSBBitReader<ForwardInputStream> bitReader(inputStream);
- auto readBit=[&]()->uint32_t
- {
- return bitReader.readBits8(1);
- };
- auto readByte=[&]()->uint8_t
- {
- return inputStream.readByte();
- };
- ForwardOutputStream outputStream(rawData,0,_rawSize);
- // Huffman decoding
- enum class Cmd
- {
- LIT=0, // 0, Literal
- MOV, // 10, Move bytes + length + distance, Get bytes if length=9 + 4bits
- MV2, // 110, Move 2 bytes
- MV3, // 1110, Move 3 bytes
- CND // 1111, Conditional copy, or EOF
-
- };
- HuffmanDecoder<Cmd> cmdDecoder
- {
- HuffmanCode<Cmd>{1,0b0000,Cmd::LIT},
- HuffmanCode<Cmd>{2,0b0010,Cmd::MOV},
- HuffmanCode<Cmd>{3,0b0110,Cmd::MV2},
- HuffmanCode<Cmd>{4,0b1110,Cmd::MV3},
- HuffmanCode<Cmd>{4,0b1111,Cmd::CND}
- };
- /* length of 9 is a marker for literals */
- HuffmanDecoder<uint8_t> lengthDecoder
- {
- HuffmanCode<uint8_t>{2,0b000,4},
- HuffmanCode<uint8_t>{2,0b010,5},
- HuffmanCode<uint8_t>{3,0b010,6},
- HuffmanCode<uint8_t>{3,0b011,7},
- HuffmanCode<uint8_t>{3,0b110,8},
- HuffmanCode<uint8_t>{3,0b111,9}
- };
-
- HuffmanDecoder<int8_t> distanceDecoder
- {
- HuffmanCode<int8_t>{1,0b000000,0},
- HuffmanCode<int8_t>{3,0b000110,1},
- HuffmanCode<int8_t>{4,0b001000,2},
- HuffmanCode<int8_t>{4,0b001001,3},
- HuffmanCode<int8_t>{5,0b010101,4},
- HuffmanCode<int8_t>{5,0b010111,5},
- HuffmanCode<int8_t>{5,0b011101,6},
- HuffmanCode<int8_t>{5,0b011111,7},
- HuffmanCode<int8_t>{6,0b101000,8},
- HuffmanCode<int8_t>{6,0b101001,9},
- HuffmanCode<int8_t>{6,0b101100,10},
- HuffmanCode<int8_t>{6,0b101101,11},
- HuffmanCode<int8_t>{6,0b111000,12},
- HuffmanCode<int8_t>{6,0b111001,13},
- HuffmanCode<int8_t>{6,0b111100,14},
- HuffmanCode<int8_t>{6,0b111101,15}
- };
- // helpers
- auto readDistance=[&]()->uint32_t
- {
- int8_t distMult=distanceDecoder.decode(readBit);
- if (distMult<0) throw DecompressionError();
- uint8_t distByte=readByte();
- return (uint32_t(distByte)|(uint32_t(distMult)<<8))+1;
- };
-
- auto moveBytes=[&](uint32_t distance,uint32_t count)->void
- {
- if (!count) throw DecompressionError();
- outputStream.copy(distance,count);
- };
- readBit();
- readBit();
- uint8_t foundChunks=0;
- bool done=false;
- while (!done && foundChunks<_chunks)
- {
- Cmd cmd=cmdDecoder.decode(readBit);
- switch (cmd) {
- case Cmd::LIT:
- outputStream.writeByte(readByte());
- break;
- case Cmd::MOV:
- {
- uint8_t count=lengthDecoder.decode(readBit);
- if (count!=9)
- moveBytes(readDistance(),count);
- else {
- uint32_t rep=0;
- for (uint32_t i=0;i<4;i++)
- rep=(rep<<1)|readBit();
- rep=(rep+3)*4;
- for (uint32_t i=0;i<rep;i++)
- outputStream.writeByte(readByte());
- }
- }
- break;
- case Cmd::MV2:
- moveBytes(uint32_t(readByte())+1,2);
- break;
- case Cmd::MV3:
- moveBytes(readDistance(),3);
- break;
- case Cmd::CND:
- {
- uint8_t count=readByte();
- if (count)
- moveBytes(readDistance(),uint32_t(count+8));
- else {
- foundChunks++;
- done=!readBit();
- }
-
- }
- break;
- }
- }
- if (!outputStream.eof() || _chunks!=foundChunks) throw DecompressionError();
- if (verify && CRC16(rawData,0,_rawSize,0)!=_rawCRC) throw VerificationError();
- }
- }
|