458 lines
11 KiB
C++
458 lines
11 KiB
C++
|
/* 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();
|
||
|
}
|
||
|
|
||
|
}
|