/* Copyright (C) Teemu Suutari */ #include #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 RNCDecompressor::create(const Buffer &packedData,bool exactSizeKnown,bool verify) { return std::make_shared(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(_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 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+1),7-i); break; } } BackwardOutputStream outputStream(rawData,0,_rawSize); HuffmanDecoder litDecoder { HuffmanCode{1,0b00,0}, HuffmanCode{2,0b10,1}, HuffmanCode{2,0b11,2} }; HuffmanDecoder lengthDecoder { HuffmanCode{1,0b0000,0}, HuffmanCode{2,0b0010,1}, HuffmanCode{3,0b0110,2}, HuffmanCode{4,0b1110,3}, HuffmanCode{4,0b1111,4} }; HuffmanDecoder distanceDecoder { HuffmanCode{1,0b00,0}, HuffmanCode{2,0b10,1}, HuffmanCode{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< 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 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;imaxDepth) 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 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 cmdDecoder { HuffmanCode{1,0b0000,Cmd::LIT}, HuffmanCode{2,0b0010,Cmd::MOV}, HuffmanCode{3,0b0110,Cmd::MV2}, HuffmanCode{4,0b1110,Cmd::MV3}, HuffmanCode{4,0b1111,Cmd::CND} }; /* length of 9 is a marker for literals */ HuffmanDecoder lengthDecoder { HuffmanCode{2,0b000,4}, HuffmanCode{2,0b010,5}, HuffmanCode{3,0b010,6}, HuffmanCode{3,0b011,7}, HuffmanCode{3,0b110,8}, HuffmanCode{3,0b111,9} }; HuffmanDecoder distanceDecoder { HuffmanCode{1,0b000000,0}, HuffmanCode{3,0b000110,1}, HuffmanCode{4,0b001000,2}, HuffmanCode{4,0b001001,3}, HuffmanCode{5,0b010101,4}, HuffmanCode{5,0b010111,5}, HuffmanCode{5,0b011101,6}, HuffmanCode{5,0b011111,7}, HuffmanCode{6,0b101000,8}, HuffmanCode{6,0b101001,9}, HuffmanCode{6,0b101100,10}, HuffmanCode{6,0b101101,11}, HuffmanCode{6,0b111000,12}, HuffmanCode{6,0b111001,13}, HuffmanCode{6,0b111100,14}, HuffmanCode{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