mirror of https://github.com/libAthena/athena.git
214 lines
9.0 KiB
C++
214 lines
9.0 KiB
C++
#include "LZ77/LZLookupTable.hpp"
|
|
#include "LZ77/LZType11.hpp"
|
|
#include <Athena/BinaryWriter.hpp>
|
|
#include <memory.h>
|
|
|
|
|
|
LZType11::LZType11(atInt32 minimumOffset, atInt32 slidingWindow, atInt32 minimumMatch, atInt32 blockSize)
|
|
: LZBase(minimumOffset,slidingWindow,minimumMatch,blockSize)
|
|
{
|
|
m_readAheadBuffer=(0xF + 0xFF + 0xFFFF + m_minMatch);
|
|
m_lookupTable.setLookAheadWindow(m_readAheadBuffer);
|
|
}
|
|
|
|
atUint32 LZType11::compress(const atUint8* src, atUint8** dst, atUint32 srcLength)
|
|
{
|
|
Athena::io::BinaryWriter outbuff("tmp");
|
|
if (srcLength>0xFFFFFF){// If length is greater than 24 bits or 16 Megs
|
|
atUint32 encodeFlag=0x11;
|
|
Athena::utility::LittleUint32(encodeFlag);
|
|
Athena::utility::LittleUint32(srcLength);//Filesize data is little endian
|
|
outbuff.writeUint32(encodeFlag);
|
|
outbuff.writeUint32(srcLength);
|
|
|
|
}
|
|
else{
|
|
atUint32 encodeSize=(srcLength<<8)|(0x11);
|
|
Athena::utility::LittleUint32(encodeSize);
|
|
outbuff.writeUint32(encodeSize);
|
|
}
|
|
|
|
atUint8 *ptrStart=(atUint8*)src;
|
|
atUint8 *ptrEnd=(atUint8*)(src+srcLength);
|
|
|
|
//At most their will be two bytes written if the bytes can be compressed. So if all bytes in the block can be compressed it would take blockSize*2 bytes
|
|
atUint8 *compressedBytes=new atUint8[m_blockSize *2];//Holds the compressed bytes yet to be written
|
|
|
|
atUint8 maxTwoByteMatch= 0xF+1;
|
|
atUint8 minThreeByteMatch=maxTwoByteMatch+1;//Minimum Three byte match is maximum TwoByte match + 1
|
|
atUint16 maxThreeByteMatch= 0xFF+minThreeByteMatch;
|
|
atUint16 minFourByteMatch=maxThreeByteMatch+1;//Minimum Four byte match is maximum Three Byte match + 1
|
|
atInt32 maxFourByteMatch=0xFFFF+minFourByteMatch;
|
|
/*
|
|
Normaliazation Example: If MIN_MATCH is 3 then 3 gets mapped to 2 and 16 gets mapped to 15.
|
|
17 gets mapped to 1 and 272 gets mapped to 255
|
|
273 gets mapped to 0 and 65808 gets mapped to 65535
|
|
|
|
|
|
A two byte match uses 4 bits
|
|
A three byte match uses 8 bits
|
|
A four byte match uses 16 bits
|
|
In each case the offset uses 12 bits
|
|
In the two byte case the length is normalized so that the first 4 bits are numbers between between 2 and 15
|
|
In the three byte case the first 4 bits are 0000
|
|
In the four byte case the first 4 bits a 0001
|
|
*/
|
|
while( ptrStart < ptrEnd )
|
|
{
|
|
atUint8 blockSize=0;
|
|
//In Binary represents 1 if byte is compressed or 0 if not compressed
|
|
//For example 01001000 means that the second and fifth byte in the blockSize from the left is compressed
|
|
atUint8 *ptrBytes=compressedBytes;
|
|
for(atInt32 i=0;i < m_blockSize;i++)
|
|
{
|
|
//length_offset searchResult=Search(filedata,ptrStart,ptrEnd);
|
|
LZLengthOffset searchResult=m_lookupTable.search(ptrStart, src, ptrEnd);
|
|
//If the number of bytes to be compressed is at least the size of the Minimum match
|
|
if(searchResult.length >= (atUint32)m_minMatch)
|
|
{ //Gotta swap the bytes since system is wii is big endian and most computers are little endian
|
|
|
|
if(searchResult.length <= maxTwoByteMatch){
|
|
atUint16 lenOff=((((searchResult.length - 1) & 0xF) << 12) | //Bits 15-12
|
|
((searchResult.offset - 1) & 0xFFF) //Bits 11-0
|
|
);
|
|
Athena::utility::BigUint16(lenOff);
|
|
memcpy(ptrBytes,&lenOff,2);
|
|
ptrBytes+=2;
|
|
}
|
|
else if(searchResult.length <= maxThreeByteMatch){
|
|
atUint32 lenOff=((((searchResult.length - minThreeByteMatch) & 0xFF)<< 12) | //Bits 20-12
|
|
((searchResult.offset - 1) & 0xFFF) //Bits 11-0
|
|
);
|
|
Athena::utility::BigUint32(lenOff);
|
|
memcpy(ptrBytes,(atUint8*)&lenOff+1,3); //Make sure to copy the lower 24 bits. 0x12345678- This statement copies 0x123456
|
|
ptrBytes+=3;
|
|
}
|
|
else if(searchResult.length <= (atUint32)maxFourByteMatch){
|
|
atUint32 lenOff=((1<<28) | //Bits 31-28 Flag to say that this is four bytes
|
|
(((searchResult.length - minFourByteMatch) & 0xFFFF)<< 12) | //Bits 28-12
|
|
((searchResult.offset - 1) & 0xFFF) //Bits 11-0
|
|
);
|
|
Athena::utility::BigUint32(lenOff);
|
|
memcpy(ptrBytes,&lenOff,4);
|
|
ptrBytes+=4;
|
|
}
|
|
ptrStart+=searchResult.length;
|
|
|
|
blockSize |=(1 << (7-i));
|
|
//Stores which of the next 8 bytes is compressed
|
|
//bit 1 for compress and bit 0 for not compressed
|
|
}
|
|
else
|
|
*ptrBytes++=*ptrStart++;
|
|
|
|
}
|
|
outbuff.writeByte(blockSize);
|
|
outbuff.writeUBytes(compressedBytes,(atUint64)(ptrBytes-compressedBytes));
|
|
|
|
}
|
|
delete []compressedBytes;
|
|
compressedBytes=NULL;
|
|
|
|
//Add zeros until the file is a multiple of 4
|
|
while((outbuff.position()%4) !=0 )
|
|
outbuff.writeByte(0);
|
|
|
|
*dst = outbuff.data();
|
|
return (atUint32)outbuff.length();
|
|
}
|
|
|
|
atUint32 LZType11::decompress(const atUint8* src, atUint8** dst, atUint32 srcLength)
|
|
{
|
|
if(*(atUint8*)(src) != 0x11)
|
|
return 0;
|
|
|
|
atUint32 uncompressedLen = *(atUint32*)(src);
|
|
Athena::utility::LittleUint32(uncompressedLen);//The compressed file has the filesize encoded in little endian
|
|
uncompressedLen = uncompressedLen >> 8; //First byte is the encode flag
|
|
atUint32 currentOffset = 4;
|
|
if(uncompressedLen==0)//If the filesize var is zero then the true filesize is over 14MB and must be read in from the next 4 bytes
|
|
{
|
|
atUint32 filesize = *(atUint32*)(src + 4);
|
|
filesize = Athena::utility::LittleUint32(filesize);
|
|
currentOffset += 4;
|
|
}
|
|
|
|
atUint8 *uncompressedData=new atUint8[uncompressedLen];
|
|
atUint8 *outputPtr=uncompressedData;
|
|
atUint8 *outputEndPtr=uncompressedData+uncompressedLen;
|
|
atUint8 *inputPtr=(atUint8*)src + currentOffset;
|
|
atUint8 *inputEndPtr=(atUint8*)src+srcLength;
|
|
|
|
LZLengthOffset decoding;
|
|
atUint8 maxTwoByteMatch= 0xF+1;
|
|
atUint8 threeByteDenorm=maxTwoByteMatch+1;//Amount to add to length when compression is 3 bytes
|
|
atUint16 maxThreeByteMatch=0xFF+threeByteDenorm;
|
|
atUint16 fourByteDenorm=maxThreeByteMatch+1;
|
|
|
|
while(inputPtr<inputEndPtr && outputPtr<outputEndPtr)
|
|
{
|
|
|
|
atUint8 isCompressed=*inputPtr++;
|
|
|
|
for(atInt32 i=0;i < m_blockSize; i++)
|
|
{
|
|
//Checks to see if the next byte is compressed by looking
|
|
//at its binary representation - E.g 10010000
|
|
//This says that the first extracted byte and the four extracted byte is compressed
|
|
if ((isCompressed>>(7-i)) & 0x1)
|
|
{
|
|
atUint8 metaDataSize=*inputPtr >> 4;//Look at the top 4 bits
|
|
if(metaDataSize >= 2){ //Two Bytes of Length/Offset MetaData
|
|
atUint16 lenOff=0;
|
|
memcpy(&lenOff,inputPtr,2);
|
|
inputPtr+=2;
|
|
Athena::utility::BigUint16(lenOff);
|
|
decoding.length=(lenOff>>12)+1;
|
|
decoding.offset=(lenOff & 0xFFF) + 1;
|
|
}
|
|
else if (metaDataSize==0){ //Three Bytes of Length/Offset MetaData
|
|
atUint32 lenOff=0;
|
|
memcpy((atUint8*)&lenOff+1,inputPtr,3);
|
|
inputPtr+=3;
|
|
Athena::utility::BigUint32(lenOff);
|
|
decoding.length=(lenOff>>12)+threeByteDenorm;
|
|
decoding.offset=(lenOff & 0xFFF) + 1;
|
|
}
|
|
else if(metaDataSize==1){ //Four Bytes of Length/Offset MetaData
|
|
atUint32 lenOff=0;
|
|
memcpy(&lenOff,inputPtr,4);
|
|
inputPtr+=4;
|
|
Athena::utility::BigUint32(lenOff);
|
|
|
|
decoding.length=((lenOff>>12) & 0xFFFF)+fourByteDenorm; //Gets rid of the Four byte flag
|
|
decoding.offset=(lenOff & 0xFFF) + 1;
|
|
}
|
|
else{
|
|
delete[] uncompressedData;
|
|
uncompressedData = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
if((outputPtr - decoding.offset) < uncompressedData){//If the offset to look for uncompressed is passed the current uncompresed data then the data is not compressed
|
|
delete []uncompressedData;
|
|
return 0;
|
|
}
|
|
for(atUint32 j=0;j<decoding.length;++j)
|
|
outputPtr[j]=(outputPtr-decoding.offset)[j];
|
|
outputPtr+=decoding.length;
|
|
}
|
|
else
|
|
*outputPtr++=*inputPtr++;
|
|
|
|
if(!(inputPtr<inputEndPtr && outputPtr<outputEndPtr))
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
*dst = uncompressedData;
|
|
return uncompressedLen;
|
|
}
|
|
|