719 lines
21 KiB
C++
Vendored
719 lines
21 KiB
C++
Vendored
/* Copyright (C) Teemu Suutari */
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
#include "DMSDecompressor.hpp"
|
|
|
|
#include "HuffmanDecoder.hpp"
|
|
#include "DynamicHuffmanDecoder.hpp"
|
|
#include "InputStream.hpp"
|
|
#include "OutputStream.hpp"
|
|
|
|
#include "common/MemoryBuffer.hpp"
|
|
#include "common/CRC16.hpp"
|
|
#include "common/OverflowCheck.hpp"
|
|
#include "common/Common.hpp"
|
|
|
|
|
|
namespace ancient::internal
|
|
{
|
|
|
|
bool DMSDecompressor::detectHeader(uint32_t hdr) noexcept
|
|
{
|
|
return hdr==FourCC("DMS!");
|
|
}
|
|
|
|
std::shared_ptr<Decompressor> DMSDecompressor::create(const Buffer &packedData,bool exactSizeKnown,bool verify)
|
|
{
|
|
return std::make_shared<DMSDecompressor>(packedData,verify);
|
|
}
|
|
|
|
DMSDecompressor::DMSDecompressor(const Buffer &packedData,bool verify) :
|
|
_packedData(packedData)
|
|
{
|
|
uint32_t hdr=packedData.readBE32(0);
|
|
if (!detectHeader(hdr) || packedData.size()<56) throw InvalidFormatError();
|
|
|
|
if (verify && CRC16(packedData,4,50,0)!=packedData.readBE16(54))
|
|
throw VerificationError();
|
|
|
|
uint16_t info=packedData.readBE16(10);
|
|
_isObsfuscated=info&2; // using 16 bit key is not encryption, it is obsfuscation
|
|
_isHD=info&16;
|
|
if (info&32) throw InvalidFormatError(); // MS-DOS disk
|
|
|
|
// packed data in header is useless, as is rawsize and track numbers
|
|
// they are not always filled
|
|
|
|
if (packedData.readBE16(50)>6) throw InvalidFormatError(); // either FMS or unknown
|
|
|
|
// now calculate the real packed size, including headers
|
|
uint32_t offset=56;
|
|
uint32_t accountedSize=0;
|
|
uint32_t lastTrackSize=0;
|
|
uint32_t numTracks=0;
|
|
uint32_t minTrack=80;
|
|
uint32_t prevTrack=0;
|
|
while (offset+20<packedData.size())
|
|
{
|
|
if (_packedData.readBE16(offset)!=MultiChar2("TR"))
|
|
{
|
|
// secondary exit criteria, should not be like this, if the header would be trustworthy
|
|
if (!accountedSize) throw InvalidFormatError();
|
|
break;
|
|
}
|
|
uint32_t trackNo=_packedData.readBE16(offset+2);
|
|
// lets not go backwards on tracks!
|
|
if (trackNo<prevTrack) break;
|
|
|
|
// header check
|
|
if (verify && CRC16(packedData,offset,18,0)!=packedData.readBE16(offset+18))
|
|
throw VerificationError();
|
|
|
|
uint8_t mode=_packedData.read8(offset+13);
|
|
if (mode>6) throw InvalidFormatError();
|
|
static const uint32_t contextSizes[7]={0,0,256,16384,16384,4096,8192};
|
|
_contextBufferSize=std::max(_contextBufferSize,contextSizes[mode]);
|
|
|
|
uint8_t flags=_packedData.read8(offset+12);
|
|
if ((mode>=2 && mode<=4) || (mode>=5 && (flags&4)))
|
|
{
|
|
_tmpBufferSize=std::max(_tmpBufferSize,uint32_t(_packedData.readBE16(offset+8)));
|
|
}
|
|
uint32_t packedChunkLength=packedData.readBE16(offset+6);
|
|
if (OverflowCheck::sum(offset,20U,packedChunkLength)>packedData.size())
|
|
throw InvalidFormatError();
|
|
if (verify && CRC16(packedData,offset+20,packedChunkLength,0)!=packedData.readBE16(offset+16))
|
|
throw VerificationError();
|
|
|
|
if (trackNo<80)
|
|
{
|
|
if (trackNo>=numTracks) lastTrackSize=_packedData.readBE16(offset+10);
|
|
minTrack=std::min(minTrack,trackNo);
|
|
numTracks=std::max(numTracks,trackNo);
|
|
prevTrack=trackNo;
|
|
}
|
|
|
|
offset+=packedChunkLength+20;
|
|
accountedSize+=packedChunkLength;
|
|
// this is the real exit critea, unfortunately
|
|
if (trackNo>=79 && trackNo<0x8000U) break;
|
|
}
|
|
uint32_t trackSize=(_isHD)?22528:11264;
|
|
_rawOffset=minTrack*trackSize;
|
|
if (minTrack>=numTracks)
|
|
throw InvalidFormatError();
|
|
_minTrack=minTrack;
|
|
_rawSize=(numTracks-minTrack)*trackSize+lastTrackSize;
|
|
_imageSize=trackSize*80;
|
|
|
|
_packedSize=offset;
|
|
if (_packedSize>getMaxPackedSize())
|
|
throw InvalidFormatError();
|
|
}
|
|
|
|
DMSDecompressor::~DMSDecompressor()
|
|
{
|
|
// nothing needed
|
|
}
|
|
|
|
const std::string &DMSDecompressor::getName() const noexcept
|
|
{
|
|
static std::string name="DMS: Disk Masher System";
|
|
return name;
|
|
}
|
|
|
|
size_t DMSDecompressor::getPackedSize() const noexcept
|
|
{
|
|
return _packedSize;
|
|
}
|
|
|
|
size_t DMSDecompressor::getRawSize() const noexcept
|
|
{
|
|
return _rawSize;
|
|
}
|
|
|
|
size_t DMSDecompressor::getImageSize() const noexcept
|
|
{
|
|
return _imageSize;
|
|
}
|
|
|
|
size_t DMSDecompressor::getImageOffset() const noexcept
|
|
{
|
|
return _rawOffset;
|
|
}
|
|
|
|
void DMSDecompressor::decompressImpl(Buffer &rawData,bool verify)
|
|
{
|
|
uint32_t restartPosition=0;
|
|
if (!_isObsfuscated)
|
|
{
|
|
decompressImpl(rawData,verify,restartPosition);
|
|
} else {
|
|
while (restartPosition<0x20000U)
|
|
{
|
|
// more than single run here is really rare. It means that first track CRC succeeds
|
|
// but later something else fails
|
|
try
|
|
{
|
|
decompressImpl(rawData,verify,restartPosition);
|
|
return;
|
|
} catch (const Buffer::Error &) {
|
|
// just continue
|
|
} catch (const Decompressor::Error &) {
|
|
// just continue
|
|
}
|
|
restartPosition++;
|
|
}
|
|
throw DecompressionError();
|
|
}
|
|
}
|
|
|
|
// TODO: Too much state for a single method. too convoluted
|
|
// needs to be split
|
|
void DMSDecompressor::decompressImpl(Buffer &rawData,bool verify,uint32_t &restartPosition)
|
|
{
|
|
if (rawData.size()<_rawSize) throw DecompressionError();
|
|
MemoryBuffer contextBuffer(_contextBufferSize);
|
|
MemoryBuffer tmpBuffer(_tmpBufferSize);
|
|
uint32_t limitedDecompress=~0U;
|
|
|
|
class UnObsfuscator
|
|
{
|
|
public:
|
|
UnObsfuscator(ForwardInputStream &inputStream) :
|
|
_inputStream(inputStream)
|
|
{
|
|
// nothing needed
|
|
}
|
|
|
|
uint8_t readByte()
|
|
{
|
|
if (_inputStream.eof()) throw ShortInputError();
|
|
uint8_t ch=_inputStream.readByte();
|
|
if (!_obsfuscate)
|
|
{
|
|
return ch;
|
|
} else {
|
|
uint8_t ret=ch^_passAccumulator;
|
|
_passAccumulator=(_passAccumulator>>1)+uint16_t(ch);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void setCode(uint16_t passAccumulator)
|
|
{
|
|
_passAccumulator=passAccumulator;
|
|
}
|
|
|
|
void setObsfuscate(bool obsfuscate) { _obsfuscate=obsfuscate; }
|
|
bool eof() const { return _inputStream.getOffset()==_inputStream.getEndOffset(); }
|
|
|
|
// not cool, but works (does not need wraparound check)
|
|
bool eofMinus1() const { return _inputStream.getOffset()+1==_inputStream.getEndOffset(); }
|
|
bool eofMinus1Plus() const { return _inputStream.getOffset()+1>=_inputStream.getEndOffset(); }
|
|
bool eofMinus2Plus() const { return _inputStream.getOffset()+2>=_inputStream.getEndOffset(); }
|
|
|
|
private:
|
|
ForwardInputStream &_inputStream;
|
|
bool _obsfuscate=false;
|
|
uint16_t _passAccumulator=0;
|
|
};
|
|
|
|
ForwardInputStream inputStream(_packedData,0,0);
|
|
UnObsfuscator inputUnObsfuscator(inputStream);
|
|
MSBBitReader<UnObsfuscator> bitReader(inputUnObsfuscator);
|
|
auto initInputStream=[&](const Buffer &buffer,uint32_t start,uint32_t length,bool obsfuscate)
|
|
{
|
|
inputStream=ForwardInputStream(buffer,start,OverflowCheck::sum(start,length));
|
|
inputUnObsfuscator.setObsfuscate(obsfuscate);
|
|
bitReader.reset();
|
|
};
|
|
auto finishStream=[&]()
|
|
{
|
|
if (_isObsfuscated && limitedDecompress==~0U)
|
|
while (!inputUnObsfuscator.eof())
|
|
inputUnObsfuscator.readByte();
|
|
};
|
|
auto readBits=[&](uint32_t count)->uint32_t
|
|
{
|
|
return bitReader.readBits8(count);
|
|
};
|
|
auto readBit=[&]()->uint32_t
|
|
{
|
|
return bitReader.readBits8(1);
|
|
};
|
|
|
|
ForwardOutputStream outputStream(rawData,0,0);
|
|
auto initOutputStream=[&](Buffer &buffer,uint32_t start,uint32_t length)
|
|
{
|
|
outputStream=ForwardOutputStream(buffer,start,OverflowCheck::sum(start,length));
|
|
};
|
|
|
|
// fill unused tracks with zeros
|
|
std::memset(rawData.data(),0,_rawSize);
|
|
|
|
auto checksum=[](const uint8_t *src,uint32_t srcLength)->uint16_t
|
|
{
|
|
uint16_t ret=0;
|
|
for (uint32_t i=0;i<srcLength;i++) ret+=uint16_t(src[i]);
|
|
return ret;
|
|
};
|
|
|
|
auto unpackNone=[&]()
|
|
{
|
|
for (uint32_t i=0;i<limitedDecompress&&!inputUnObsfuscator.eof();i++)
|
|
outputStream.writeByte(inputUnObsfuscator.readByte());
|
|
};
|
|
|
|
// same as simple
|
|
auto unRLE=[&](bool lastCharMissing)->uint32_t
|
|
{
|
|
// hacky, hacky, hacky
|
|
while (!inputUnObsfuscator.eof())
|
|
{
|
|
if (outputStream.getOffset()>=limitedDecompress) return 0;
|
|
if (lastCharMissing && inputUnObsfuscator.eofMinus1())
|
|
{
|
|
if (outputStream.getOffset()+1!=outputStream.getEndOffset())
|
|
throw DecompressionError();
|
|
return 1;
|
|
}
|
|
uint8_t ch=inputUnObsfuscator.readByte();
|
|
uint32_t count=1;
|
|
if (ch==0x90U)
|
|
{
|
|
if (inputUnObsfuscator.eof()) throw DecompressionError();
|
|
if (lastCharMissing && inputUnObsfuscator.eofMinus1())
|
|
{
|
|
// only possible way this can work
|
|
if (outputStream.getOffset()+1!=outputStream.getEndOffset()) throw DecompressionError();
|
|
count=0;
|
|
} else count=inputUnObsfuscator.readByte();
|
|
if (!count)
|
|
{
|
|
count=1;
|
|
} else {
|
|
|
|
if (inputUnObsfuscator.eof()) throw DecompressionError();
|
|
if (lastCharMissing && inputUnObsfuscator.eofMinus1())
|
|
{
|
|
if (count==0xffU || outputStream.getOffset()+count!=outputStream.getEndOffset()) throw DecompressionError();
|
|
return count;
|
|
}
|
|
ch=inputUnObsfuscator.readByte();
|
|
}
|
|
if (count==0xffU)
|
|
{
|
|
if (inputUnObsfuscator.eofMinus1Plus()) throw DecompressionError();
|
|
if (lastCharMissing && inputUnObsfuscator.eofMinus2Plus())
|
|
{
|
|
count=uint32_t(outputStream.getEndOffset()-outputStream.getOffset());
|
|
} else {
|
|
count=uint32_t(inputUnObsfuscator.readByte())<<8;
|
|
count|=inputUnObsfuscator.readByte();
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t i=0;i<count;i++) outputStream.writeByte(ch);
|
|
}
|
|
if (!outputStream.eof()) throw DecompressionError();
|
|
return 0;
|
|
};
|
|
|
|
bool doInitContext=true;
|
|
uint8_t *contextBufferPtr=contextBuffer.data();
|
|
// context used is 256 bytes
|
|
uint8_t quickContextLocation;
|
|
// context used is 16384 bytes
|
|
uint32_t mediumContextLocation;
|
|
uint32_t deepContextLocation;
|
|
// context used is 4096/8192 bytes
|
|
uint32_t heavyContextLocation;
|
|
std::unique_ptr<DynamicHuffmanDecoder<314>> deepDecoder;
|
|
auto initContext=[&]()
|
|
{
|
|
if (doInitContext)
|
|
{
|
|
if (_contextBufferSize) std::memset(contextBuffer.data(),0,_contextBufferSize);
|
|
quickContextLocation=251;
|
|
mediumContextLocation=16318;
|
|
deepContextLocation=16324;
|
|
deepDecoder.reset();
|
|
heavyContextLocation=0;
|
|
doInitContext=false;
|
|
}
|
|
};
|
|
|
|
auto unpackQuick=[&]()
|
|
{
|
|
initContext();
|
|
|
|
while (!outputStream.eof())
|
|
{
|
|
if (outputStream.getOffset()>=limitedDecompress) return;
|
|
if (readBits(1))
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[quickContextLocation++]=readBits(8));
|
|
} else {
|
|
uint32_t count=readBits(2)+2;
|
|
uint8_t offset=quickContextLocation-readBits(8)-1;
|
|
for (uint32_t i=0;i<count;i++)
|
|
outputStream.writeByte(contextBufferPtr[quickContextLocation++]=contextBufferPtr[(i+offset)&0xffU]);
|
|
}
|
|
}
|
|
quickContextLocation+=5;
|
|
};
|
|
|
|
static const uint8_t lengthTable[256]={
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
|
|
10,10,10,10,10,10,10,10, 11,11,11,11,11,11,11,11,
|
|
12,12,12,12,13,13,13,13, 14,14,14,14,15,15,15,15,
|
|
16,16,16,16,17,17,17,17, 18,18,18,18,19,19,19,19,
|
|
20,20,20,20,21,21,21,21, 22,22,22,22,23,23,23,23,
|
|
24,24,25,25,26,26,27,27, 28,28,29,29,30,30,31,31,
|
|
32,32,33,33,34,34,35,35, 36,36,37,37,38,38,39,39,
|
|
40,40,41,41,42,42,43,43, 44,44,45,45,46,46,47,47,
|
|
48,49,50,51,52,53,54,55, 56,57,58,59,60,61,62,63};
|
|
|
|
static const uint8_t bitLengthTable[256]={
|
|
3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,
|
|
3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,
|
|
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
|
|
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
|
|
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
|
|
5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,
|
|
5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,
|
|
5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,
|
|
5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,
|
|
6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,
|
|
6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,
|
|
6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,
|
|
7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,
|
|
8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8};
|
|
|
|
auto decodeLengthValueHalf=[&](uint8_t code)->uint32_t
|
|
{
|
|
return (((code<<bitLengthTable[code])|readBits(bitLengthTable[code]))&0xffU);
|
|
};
|
|
|
|
auto decodeLengthValueFull=[&](uint8_t code)->uint32_t
|
|
{
|
|
return (uint32_t(lengthTable[code])<<8)|
|
|
uint32_t(((code<<bitLengthTable[code])|readBits(bitLengthTable[code]))&0xffU);
|
|
};
|
|
|
|
auto unpackMedium=[&]()
|
|
{
|
|
initContext();
|
|
|
|
while (!outputStream.eof())
|
|
{
|
|
if (outputStream.getOffset()>=limitedDecompress) return;
|
|
if (readBits(1))
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[mediumContextLocation++]=readBits(8));
|
|
mediumContextLocation&=0x3fffU;
|
|
} else {
|
|
uint32_t code=readBits(8);
|
|
uint32_t count=lengthTable[code]+3;
|
|
|
|
uint32_t tmp=decodeLengthValueFull(decodeLengthValueHalf(code));
|
|
|
|
uint32_t offset=mediumContextLocation-tmp-1;
|
|
for (uint32_t i=0;i<count;i++)
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[mediumContextLocation++]=contextBufferPtr[(i+offset)&0x3fffU]);
|
|
mediumContextLocation&=0x3fffU;
|
|
}
|
|
}
|
|
}
|
|
mediumContextLocation+=66;
|
|
mediumContextLocation&=0x3fffU;
|
|
};
|
|
|
|
auto unpackDeep=[&]()
|
|
{
|
|
initContext();
|
|
if (!deepDecoder) deepDecoder=std::make_unique<DynamicHuffmanDecoder<314>>();
|
|
|
|
while (!outputStream.eof())
|
|
{
|
|
if (outputStream.getOffset()>=limitedDecompress) return;
|
|
uint32_t symbol=deepDecoder->decode(readBit);
|
|
if (deepDecoder->getMaxFrequency()==0x8000U) deepDecoder->halve();
|
|
deepDecoder->update(symbol);
|
|
if (symbol<256)
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[deepContextLocation++]=symbol);
|
|
deepContextLocation&=0x3fffU;
|
|
} else {
|
|
uint32_t count=symbol-253; // minimum repeat is 3
|
|
uint32_t offset=deepContextLocation-decodeLengthValueFull(readBits(8))-1;
|
|
|
|
for (uint32_t i=0;i<count;i++)
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[deepContextLocation++]=contextBufferPtr[(i+offset)&0x3fffU]);
|
|
deepContextLocation&=0x3fffU;
|
|
}
|
|
}
|
|
}
|
|
deepContextLocation+=60;
|
|
deepContextLocation&=0x3fffU;
|
|
};
|
|
|
|
// these are not part of the initContext like other methods
|
|
std::unique_ptr<OptionalHuffmanDecoder<uint32_t>> symbolDecoder,offsetDecoder;
|
|
bool heavyLastInitialized=false; // this is part of initContext on some implementations. screwy!!!
|
|
uint32_t heavyLastOffset;
|
|
auto unpackHeavy=[&](bool initTables,bool use8kDict)
|
|
{
|
|
initContext();
|
|
// well, this works. Why this works? dunno
|
|
if (!heavyLastInitialized)
|
|
{
|
|
heavyLastOffset=use8kDict?0U:~0U;
|
|
heavyLastInitialized=true;
|
|
}
|
|
|
|
auto readTable=[&](std::unique_ptr<OptionalHuffmanDecoder<uint32_t>> &decoder,uint32_t countBits,uint32_t valueBits)
|
|
{
|
|
decoder=std::make_unique<OptionalHuffmanDecoder<uint32_t>>();
|
|
uint32_t count=readBits(countBits);
|
|
if (count)
|
|
{
|
|
uint8_t lengthBuffer[512];
|
|
// in order to speed up the deObsfuscation, do not send the hopeless
|
|
// data into slow CreateOrderlyHuffmanTable
|
|
uint64_t sum=0;
|
|
for (uint32_t i=0;i<count;i++)
|
|
{
|
|
uint32_t bits=readBits(valueBits);
|
|
if (bits)
|
|
{
|
|
sum+=uint64_t(1U)<<(32-bits);
|
|
if (sum>(uint64_t(1U)<<32))
|
|
throw DecompressionError();
|
|
}
|
|
lengthBuffer[i]=bits;
|
|
}
|
|
decoder->createOrderlyHuffmanTable(lengthBuffer,count);
|
|
} else {
|
|
uint32_t index=readBits(countBits);
|
|
decoder->setEmpty(index);
|
|
}
|
|
};
|
|
|
|
if (initTables)
|
|
{
|
|
readTable(symbolDecoder,9,5);
|
|
readTable(offsetDecoder,5,4);
|
|
}
|
|
|
|
uint32_t mask=use8kDict?0x1fffU:0xfffU;
|
|
uint32_t bitLength=use8kDict?14:13;
|
|
|
|
while (!outputStream.eof())
|
|
{
|
|
if (outputStream.getOffset()>=limitedDecompress) return;
|
|
uint32_t symbol=symbolDecoder->decode(readBit);
|
|
if (symbol<256)
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[heavyContextLocation++]=symbol);
|
|
heavyContextLocation&=mask;
|
|
} else {
|
|
uint32_t count=symbol-253; // minimum repeat is 3
|
|
uint32_t offsetLength=offsetDecoder->decode(readBit);
|
|
uint32_t rawOffset=heavyLastOffset;
|
|
if (offsetLength!=bitLength)
|
|
{
|
|
if (offsetLength) rawOffset=(1<<(offsetLength-1))|readBits(offsetLength-1);
|
|
else rawOffset=0;
|
|
heavyLastOffset=rawOffset;
|
|
}
|
|
uint32_t offset=heavyContextLocation-rawOffset-1;
|
|
for (uint32_t i=0;i<count;i++)
|
|
{
|
|
outputStream.writeByte(contextBufferPtr[heavyContextLocation++]=contextBufferPtr[(i+offset)&mask]);
|
|
heavyContextLocation&=mask;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
uint32_t trackLength=(_isHD)?22528:11264;
|
|
for (uint32_t packedOffset=56,packedChunkLength=0;packedOffset!=_packedSize;packedOffset=OverflowCheck::sum(packedOffset,20U,packedChunkLength))
|
|
{
|
|
// There are some info tracks, at -1 or 80. ignore those (if still present)
|
|
uint16_t trackNo=_packedData.readBE16(packedOffset+2);
|
|
packedChunkLength=_packedData.readBE16(packedOffset+6);
|
|
if (trackNo==80) break; // should not happen, this is already excluded
|
|
// even though only -1 should be used I've seen -2 as well. ignore all negatives
|
|
uint32_t tmpChunkLength=_packedData.readBE16(packedOffset+8); // after the first unpack (if twostage)
|
|
uint32_t rawChunkLength=_packedData.readBE16(packedOffset+10); // after final unpack
|
|
uint8_t flags=_packedData.read8(packedOffset+12);
|
|
uint8_t mode=_packedData.read8(packedOffset+13);
|
|
|
|
// could affect context, but in practice they are separate, even though there is no explicit reset
|
|
// deal with decompression though...
|
|
if (trackNo>=0x8000U)
|
|
{
|
|
initInputStream(_packedData,packedOffset+20,packedChunkLength,_isObsfuscated);
|
|
finishStream();
|
|
continue;
|
|
}
|
|
if (rawChunkLength>trackLength) throw DecompressionError();
|
|
if (trackNo>80) throw DecompressionError(); // should not happen, already excluded
|
|
|
|
uint32_t dataOffset=trackNo*trackLength;
|
|
if (_rawOffset>dataOffset) throw DecompressionError();
|
|
|
|
// this is screwy, but it is, what it is
|
|
auto processBlock=[&](bool doRLE,auto func,auto&&... params)
|
|
{
|
|
initInputStream(_packedData,packedOffset+20,packedChunkLength,_isObsfuscated);
|
|
if (doRLE)
|
|
{
|
|
try
|
|
{
|
|
initOutputStream(tmpBuffer,0,tmpChunkLength);
|
|
func(params...);
|
|
finishStream();
|
|
initInputStream(tmpBuffer,0,tmpChunkLength,false);
|
|
initOutputStream(rawData,dataOffset-_rawOffset,rawChunkLength);
|
|
unRLE(false);
|
|
} catch (const ShortInputError &) {
|
|
// if this error happens on repeat/offset instead of char, though luck :(
|
|
// missing last char on src we can fix :)
|
|
initInputStream(tmpBuffer,0,tmpChunkLength,false);
|
|
initOutputStream(rawData,dataOffset-_rawOffset,rawChunkLength);
|
|
uint32_t missingNo=unRLE(true);
|
|
if (missingNo)
|
|
{
|
|
uint32_t protoSum=checksum(&rawData[dataOffset-_rawOffset],rawChunkLength-missingNo);
|
|
uint32_t fileSum=_packedData.readBE16(packedOffset+14);
|
|
uint8_t ch=((fileSum>=protoSum)?fileSum-protoSum:(0x10000U+fileSum-protoSum))/missingNo;
|
|
for (uint32_t i=0;i<missingNo;i++) outputStream.writeByte(ch);
|
|
} else throw DecompressionError();
|
|
}
|
|
} else {
|
|
try
|
|
{
|
|
initOutputStream(rawData,dataOffset-_rawOffset,rawChunkLength);
|
|
func(params...);
|
|
} catch (const ShortInputError &) {
|
|
// same deal
|
|
if (outputStream.getOffset()+1!=rawChunkLength || _isObsfuscated) throw DecompressionError();
|
|
uint32_t protoSum=checksum(&rawData[dataOffset-_rawOffset],rawChunkLength-1);
|
|
uint32_t fileSum=_packedData.readBE16(packedOffset+14);
|
|
uint8_t ch=fileSum-protoSum;
|
|
outputStream.writeByte(ch);
|
|
}
|
|
}
|
|
finishStream();
|
|
};
|
|
|
|
auto processBlockCode=[&](bool doRLE,auto func,auto&&... params)
|
|
{
|
|
if (!_isObsfuscated || trackNo!=_minTrack) return processBlock(doRLE,func,params...);
|
|
|
|
// fast try
|
|
if (!trackNo && restartPosition<0x10000U) for (;restartPosition<0x10000U;restartPosition++)
|
|
{
|
|
try
|
|
{
|
|
doInitContext=true;
|
|
inputUnObsfuscator.setCode(restartPosition);
|
|
limitedDecompress=8;
|
|
processBlock(doRLE,func,params...);
|
|
if ((rawData.readBE32(0)&0xffff'ff00U)!=FourCC("DOS\0")) continue;
|
|
|
|
// now see if the candidate is any good
|
|
doInitContext=true;
|
|
inputUnObsfuscator.setCode(restartPosition);
|
|
limitedDecompress=~0U;
|
|
processBlock(doRLE,func,params...);
|
|
if (checksum(&rawData[dataOffset-_rawOffset],rawChunkLength)!=_packedData.readBE16(packedOffset+14)) continue;
|
|
return;
|
|
} catch (const Buffer::Error &) {
|
|
// just continue
|
|
} catch (const Decompressor::Error &) {
|
|
// just continue
|
|
}
|
|
}
|
|
|
|
// slow round
|
|
limitedDecompress=~0U;
|
|
for (;restartPosition<0x20000U;restartPosition++)
|
|
{
|
|
try
|
|
{
|
|
doInitContext=true;
|
|
inputUnObsfuscator.setCode(restartPosition);
|
|
processBlock(doRLE,func,params...);
|
|
if (checksum(&rawData[dataOffset-_rawOffset],rawChunkLength)!=_packedData.readBE16(packedOffset+14)) continue;
|
|
return;
|
|
} catch (const Buffer::Error &) {
|
|
// just continue
|
|
} catch (const Decompressor::Error &) {
|
|
// just continue
|
|
}
|
|
}
|
|
throw DecompressionError();
|
|
};
|
|
|
|
switch (mode)
|
|
{
|
|
case 0:
|
|
processBlockCode(false,unpackNone);
|
|
rawChunkLength=packedChunkLength;
|
|
break;
|
|
|
|
case 1:
|
|
processBlockCode(false,unRLE,false);
|
|
break;
|
|
|
|
case 2:
|
|
processBlockCode(true,unpackQuick);
|
|
break;
|
|
|
|
case 3:
|
|
processBlockCode(true,unpackMedium);
|
|
break;
|
|
|
|
case 4:
|
|
processBlockCode(true,unpackDeep);
|
|
break;
|
|
|
|
// heavy flags:
|
|
// 2: (re-)initialize/read tables
|
|
// 4: do RLE
|
|
// heavy1 uses 4k dictionary (mode 5), whereas heavy2 uses 8k dictionary
|
|
case 5:
|
|
case 6:
|
|
processBlockCode(flags&4,unpackHeavy,flags&2,mode==6);
|
|
break;
|
|
|
|
default:
|
|
throw DecompressionError();
|
|
}
|
|
if (!(flags&1)) doInitContext=true;
|
|
|
|
if (verify && checksum(&rawData[dataOffset-_rawOffset],rawChunkLength)!=_packedData.readBE16(packedOffset+14))
|
|
throw VerificationError();
|
|
}
|
|
}
|
|
|
|
}
|