mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-22 18:37:01 +00:00
IMMORTAL: Rewrite compression.cpp to be accurate
This commit is contained in:
parent
02bfa90db2
commit
7dc88a72d1
@ -29,14 +29,42 @@
|
|||||||
*/
|
*/
|
||||||
namespace Immortal {
|
namespace Immortal {
|
||||||
|
|
||||||
Common::SeekableReadStream *ImmortalEngine::unCompress(Common::File *src, int srcLen) {
|
enum codeMask {
|
||||||
|
kMaskMSBS = 0xF000, // Code link is Most significant bits
|
||||||
|
kMaskLSBS = 0xFF00, // K link is Least significant bits
|
||||||
|
kMaskCode = 0x0FFF // Code is 12 bit
|
||||||
|
};
|
||||||
|
|
||||||
|
Common::SeekableReadStream *ImmortalEngine::unCompress(Common::File *source, int lSource) {
|
||||||
/* Note: this function does not seek() in the file, which means
|
/* Note: this function does not seek() in the file, which means
|
||||||
* that if there is a header on the data, the expectation is that
|
* that if there is a header on the data, the expectation is that
|
||||||
* seek() was already used to move past the header before this function.
|
* seek() was already used to move past the header before this function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Other notes:
|
||||||
|
* Tk is k in (w,k)
|
||||||
|
* Link is spread out between code and tk, where code has the most significant 4 bits, and tk has the least significant 8
|
||||||
|
* Codes contains the keys (plus link codes) for the substring values of the dictionary and can be up to 12 bits (4096 total entries) in size
|
||||||
|
* Tk contains byte values from the compressed data (plus link codes)
|
||||||
|
* Stack contains the currently being recreated string before it gets sent to the output
|
||||||
|
*/
|
||||||
|
|
||||||
|
// In the source, the data allocated here is a pointer passed to the function, but it's only used by this anyway
|
||||||
|
uint16 *pCodes = (uint16 *)malloc(k8K); // The Codes stack has 8 * 1024 bytes allocated
|
||||||
|
uint16 *pTk = (uint16 *)malloc(k8K); // The Tk has 8 * 1024 bytes allocated
|
||||||
|
uint16 pStack[k8K]; // In the source, the stack has the rest of the 20K. That's way more than it needs though, so we're just giving it 8k for now
|
||||||
|
|
||||||
|
uint16 oldCode = 0;
|
||||||
|
uint16 finChar = 0;
|
||||||
|
uint16 topStack = 0;
|
||||||
|
uint16 evenOdd = 0;
|
||||||
|
uint16 myCode = 0;
|
||||||
|
uint16 inCode = 0;
|
||||||
|
uint16 findEmpty = 0;
|
||||||
|
uint16 index = 0;
|
||||||
|
|
||||||
// If the source data has no length, we certainly do not want to decompress it
|
// If the source data has no length, we certainly do not want to decompress it
|
||||||
if (srcLen == 0) {
|
if (lSource == 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,216 +72,244 @@ Common::SeekableReadStream *ImmortalEngine::unCompress(Common::File *src, int sr
|
|||||||
* We do not want it to be deleted from scope, as this location is where
|
* We do not want it to be deleted from scope, as this location is where
|
||||||
* the readstream being returned will point to.
|
* the readstream being returned will point to.
|
||||||
*/
|
*/
|
||||||
Common::MemoryWriteStreamDynamic dstW(DisposeAfterUse::NO);
|
Common::MemoryWriteStreamDynamic dest(DisposeAfterUse::NO);
|
||||||
|
|
||||||
// The 20k bytes of memory that compression gets allocated to work with for the dictionary and the stack of chars
|
/* In the source we save a backup of the starting pointer to the destination, which is increased
|
||||||
uint16 start[0x4000]; // Really needs a better name, remember to do this future me
|
* as more data is added to it, so that the final length can be dest - destBkp. However in
|
||||||
uint16 ptk[0x4000]; // Pointer To Keys? Also needs a better name
|
* our case, the MemoryReadStream already has a size associated with it.
|
||||||
byte stack[0x4000]; // Stack of chars to be stored
|
*/
|
||||||
|
|
||||||
// These are the main variables we'll need for this
|
// Clear the dictionary
|
||||||
uint16 findEmpty;
|
setUpDictionary(pCodes, pTk, findEmpty);
|
||||||
uint16 code; // Needs to be ASL to index with
|
evenOdd = 0;
|
||||||
uint16 inputCode;
|
topStack = 0;
|
||||||
uint16 finalChar;
|
|
||||||
uint16 myCode; // Silly name is silly
|
|
||||||
uint16 oldCode;
|
|
||||||
uint16 index; // The Y register was used to index the byte array's, this will sort of take its place
|
|
||||||
uint16 evenOdd = 0;
|
|
||||||
uint16 topStack = 0;
|
|
||||||
|
|
||||||
byte outByte; // If only we could SEP #$20 like the 65816
|
// Get the initial input (always 0?)
|
||||||
|
inputCode(finChar, lSource, source, evenOdd);
|
||||||
|
oldCode = finChar;
|
||||||
|
myCode = oldCode;
|
||||||
|
|
||||||
setupDictionary(start, ptk, findEmpty); // Clear the dictionary and also set findEmpty to 8k
|
// (byte) is basically the same as the SEP #$20 : STA : REP #$20
|
||||||
bool carry = true; // This will represent the carry flag so we can make this a clean loop
|
dest.writeByte((byte)myCode);
|
||||||
|
|
||||||
code = getInputCode(carry, src, srcLen, evenOdd); // Get the first code
|
// Loops until it gets no more input codes (ie. length of source is 0)
|
||||||
if (carry == false) {
|
while (inputCode(inCode, lSource, source, evenOdd) == 0) {
|
||||||
return nullptr; // This is essentially the same as the first error check, but the source returns an error code and didn't even check it here so we might as well
|
myCode = inCode;
|
||||||
}
|
|
||||||
|
|
||||||
finalChar = code;
|
// The source uses the Y register for this
|
||||||
oldCode = code;
|
// We can rearrange this a little to avoid using an extra variable, but for now we're pretending index is the register
|
||||||
myCode = code;
|
index = inCode;
|
||||||
|
|
||||||
outByte = code & kMaskLow;
|
/* Check if the code is defined (has links for the linked list).
|
||||||
dstW.writeByte(outByte); // Take just the lower byte and write it the output
|
* We do this by grabbing the link portion from the code,
|
||||||
|
* then adding the Tk, and grabbing just the link portion.
|
||||||
// :nextcode
|
* This way, if either of the link codes exists, we know it's defined,
|
||||||
while (carry == true) {
|
* otherwise you just get zeros.
|
||||||
|
* This special case is for a string which is the same as the last string,
|
||||||
code = getInputCode(carry, src, srcLen, evenOdd); // Get the next code
|
* but with the first char duplicated and added to the end (how common can that possibly be??)
|
||||||
if (carry == true) {
|
*/
|
||||||
|
if ((((pCodes[index] & kMaskMSBS) | pTk[index]) & kMaskLSBS) == 0) {
|
||||||
index = code << 1;
|
// Push the last char of this string, which is the same as the first of the previous one
|
||||||
inputCode = code;
|
pStack[topStack] = finChar;
|
||||||
myCode = code;
|
topStack++;
|
||||||
|
myCode = oldCode;
|
||||||
// Split up the conditional statement to be easier to follow
|
|
||||||
uint16 cond;
|
|
||||||
cond = start[index] & kMaskLast;
|
|
||||||
cond |= ptk[index];
|
|
||||||
|
|
||||||
if ((cond & kMaskHigh) == 0) { // Empty code
|
|
||||||
index = topStack;
|
|
||||||
outByte = finalChar & kMaskLow;
|
|
||||||
stack[index] = outByte;
|
|
||||||
topStack++;
|
|
||||||
myCode = oldCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// :nextsymbol
|
|
||||||
index = myCode << 1;
|
|
||||||
while (index >= 0x200) {
|
|
||||||
myCode = start[index] & kMask12Bit;
|
|
||||||
outByte = ptk[index] & kMaskLow;
|
|
||||||
index = topStack;
|
|
||||||
stack[index] = outByte;
|
|
||||||
topStack++;
|
|
||||||
index = myCode << 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// :singlechar
|
|
||||||
finalChar = (myCode >> 1);
|
|
||||||
outByte = finalChar & kMaskLow;
|
|
||||||
dstW.writeByte(outByte);
|
|
||||||
|
|
||||||
// :dump
|
|
||||||
while (topStack != 0xFFFF) { // Dump the chars on the stack into the output file
|
|
||||||
outByte = stack[topStack] & kMaskLow;
|
|
||||||
dstW.writeByte(outByte);
|
|
||||||
topStack--;
|
|
||||||
}
|
|
||||||
|
|
||||||
topStack = 0;
|
|
||||||
code = getMember(oldCode, finalChar, findEmpty, start, ptk);
|
|
||||||
oldCode = inputCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The code is defined, but it could be either a single char or a multi char
|
||||||
|
// If the index into the dictionary is above 100, it's a multi character substring
|
||||||
|
while ((myCode) >= 0x100) {
|
||||||
|
index = myCode;
|
||||||
|
myCode = pCodes[index] & kMaskCode;
|
||||||
|
pStack[topStack] = pTk[index] & kMaskLow;
|
||||||
|
topStack++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it's a single char
|
||||||
|
finChar = myCode;
|
||||||
|
|
||||||
|
// which we write to the output
|
||||||
|
dest.writeByte((byte)myCode);
|
||||||
|
|
||||||
|
// Dump the stack
|
||||||
|
index = topStack;
|
||||||
|
index--;
|
||||||
|
while (index < 0x8000) {
|
||||||
|
dest.writeByte((byte)pStack[index]);
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
topStack = 0;
|
||||||
|
|
||||||
|
// Hash the old code with the current char, if it isn't in the dictionary, append it
|
||||||
|
member(oldCode, finChar, pCodes, pTk, findEmpty, index);
|
||||||
|
|
||||||
|
// Set up the current code as the old code for the next code
|
||||||
|
oldCode = inCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return a readstream with a pointer to the data in the write stream.
|
/* Return a readstream with a pointer to the data in the write stream.
|
||||||
* This one we do want to dispose after using, because it will be in the scope of the engine itself
|
* This one we do want to dispose after using, because it will be in the scope of the engine itself
|
||||||
*/
|
*/
|
||||||
return new Common::MemoryReadStream(dstW.getData(), dstW.size(), DisposeAfterUse::YES);
|
return new Common::MemoryReadStream(dest.getData(), dest.size(), DisposeAfterUse::YES);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImmortalEngine::setupDictionary(uint16 start[], uint16 ptk[], uint16 &findEmpty) {
|
/* Clear the tables and mark the first 256 bytes of the char table as used */
|
||||||
// Clear the whole dictionary
|
void ImmortalEngine::setUpDictionary(uint16 *pCodes, uint16 *pTk, uint16 &findEmpty) {
|
||||||
for (int i = 0x3FFF; i >= 0; i--) {
|
// Clear the tables completely (4095 entries, same as the mask for codes)
|
||||||
start[i] = 0;
|
for (int i = kMaskCode; i >= 0; i -= 1) {
|
||||||
ptk[i] = 0;
|
pCodes[i] = 0;
|
||||||
|
pTk[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the initial 256 bytes to be value 256, these are the characters without extensions
|
// Mark the first 0x100 as used for uncompress
|
||||||
for (int i = 255; i >= 0; i--) {
|
for (int i = 0xFF; i >= 0; i -= 1) {
|
||||||
ptk[i] = 256;
|
pTk[i] = 0x100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This shouldn't really be done inside the function, but for the sake of consistency with the source, we will
|
// findEmpty is a pointer for finding empty slots, so it starts at the end of the data (data is 2 bytes wide, so it's 4k instead of 8k)
|
||||||
findEmpty = 0x8000;
|
findEmpty = k4K;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ImmortalEngine::getInputCode(bool &carry, Common::File *src, int &srcLen, uint16 &evenOdd) {
|
/* Get a code from the input stream. 1 = no more codes, 0 = got code
|
||||||
// Check if we're at the end of the file
|
* On even iterations, we grab the first word.
|
||||||
if (srcLen == 0) {
|
* On odd iterations, we grab the word starting from the second byte of the previous word
|
||||||
carry = false;
|
*/
|
||||||
return 0;
|
int ImmortalEngine::inputCode(uint16 &outCode, int &lSource, Common::File *source, uint16 &evenOdd) {
|
||||||
|
// If length is 0, we're done getting codes
|
||||||
|
if (lSource == 0) {
|
||||||
|
// No more codes
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 c;
|
// Even
|
||||||
if (evenOdd != 0) { // Odd
|
if (evenOdd == 0) {
|
||||||
srcLen--;
|
lSource -= 2; // Even number of bytes, decrease by 2
|
||||||
|
evenOdd++; // Next alignment will be odd
|
||||||
|
|
||||||
|
/* The codes are stored in 12 bits, so 3 bytes = 2 codes
|
||||||
|
* nnnn nnnn [nnnn cccc cccc cccc] & 0x0FFF
|
||||||
|
* nnnn nnnn [0000 cccc cccc cccc]
|
||||||
|
*/
|
||||||
|
outCode = source->readUint16LE() & kMaskCode;
|
||||||
|
source->seek(-1, SEEK_CUR);
|
||||||
|
|
||||||
|
// Odd
|
||||||
|
} else {
|
||||||
|
lSource--;
|
||||||
evenOdd--;
|
evenOdd--;
|
||||||
c = (src->readUint16BE() >> 3) & 0x00FE; // & #-1-1
|
/* This grabs the next code which is made up of the previous code's second byte
|
||||||
} else { // Even
|
* plus the current code's byte + the next 2 byte value
|
||||||
srcLen -= 2;
|
* [nnnn nnnn nnnn cccc] cccc cccc >> 3
|
||||||
evenOdd++;
|
* [000n nnnn nnnn nnnc] cccc cccc & 0xFFFE <- this is done so the Y register has code * 2
|
||||||
c = (src->readUint16BE() & kMask12Bit) << 1;
|
* [000n nnnn nnnn nnn0] cccc cccc >> 1 <- in our case, we could have just done code >> 4
|
||||||
src->seek(-1, SEEK_CUR);
|
* [0000 nnnn nnnn nnnn]
|
||||||
|
*/
|
||||||
|
outCode = ((source->readUint16LE() >> 3) & 0xFFFE) >> 1;
|
||||||
}
|
}
|
||||||
return c;
|
|
||||||
|
// We have a good code, no error
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 ImmortalEngine::getMember(uint16 codeW, uint16 k, uint16 &findEmpty, uint16 start[], uint16 ptk[]) {
|
int ImmortalEngine::member(uint16 &codeW, uint16 &k, uint16 *pCodes, uint16 *pTk, uint16 &findEmpty, uint16 &index) {
|
||||||
// This function is effectively void, as the return value is only used in compression
|
// Step one is to make a hash value out of W (oldCode) and k (finChar)
|
||||||
|
index = ((((((k << 3) ^ k) << 1) ^ k) ^ codeW));
|
||||||
|
|
||||||
// k and codeW are local variables with the value of oldCode and finalChar
|
// The hash value has to be larger than 200 because that's where the single chars are
|
||||||
|
if (index < 0x100) {
|
||||||
uint16 hash;
|
index += 0x100;
|
||||||
uint16 tmp;
|
|
||||||
bool ag = true;
|
|
||||||
|
|
||||||
hash = (k << 3) ^ k;
|
|
||||||
hash = (hash << 1) ^ codeW;
|
|
||||||
hash <<= 1;
|
|
||||||
|
|
||||||
hash = (hash >= 0x200) ? hash : hash + 0x200;
|
|
||||||
|
|
||||||
uint16 a = start[hash] & 0x0F00;
|
|
||||||
uint16 b = ptk[hash] & kMaskHigh;
|
|
||||||
if (a | b) {
|
|
||||||
start[hash] = codeW;
|
|
||||||
ptk[hash] = k | 0x100;
|
|
||||||
return ptk[hash];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This loop is a bit wacky, due to the way the jumps were stuctured in the source
|
if ((((pCodes[index] & kMaskMSBS) | pTk[index]) & kMaskLSBS) == 0) {
|
||||||
while (ag == true) {
|
// There was no link, so we insert the key, mark the table as used, with no link
|
||||||
if ((start[hash] & kMask12Bit) == codeW) {
|
pCodes[index] = codeW;
|
||||||
if ((ptk[hash] & kMaskLow) == k) {
|
pTk[index] = k | 0x100;
|
||||||
return hash >> 1;
|
|
||||||
|
// Code not found, return error
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a link, so it's not empty
|
||||||
|
// This is a bad loop, because there is no safe way out if the data isn't right, but it's the source logic
|
||||||
|
// If there is anything corrupted in the data, the game will get stuck forever
|
||||||
|
while (true) {
|
||||||
|
uint16 tmp = 0;
|
||||||
|
|
||||||
|
// If the code matches
|
||||||
|
if ((pCodes[index] & kMaskCode) == codeW) {
|
||||||
|
// And k also matches
|
||||||
|
if ((pTk[index] & kMaskLow) == k) {
|
||||||
|
// Then entry is found, return no error
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp = start[hash] & kMaskLast;
|
// Entry was used, but it is not holding the desired key
|
||||||
if (tmp == 0) {
|
// Follow link to next entry, if there is no next entry, append to the list
|
||||||
// I've separated this into it's own function for the sake of this loop being readable
|
if ((pCodes[index] & kMaskMSBS) == 0) {
|
||||||
appendList(codeW, k, hash, findEmpty, start, ptk, tmp);
|
// Find an empty entry and link it to the last entry in the chain, then put the data in the new entry
|
||||||
ag = false;
|
uint16 prev = index;
|
||||||
|
if (findEmpty >= 0x100) {
|
||||||
|
// Table is not full, keep looking
|
||||||
|
do {
|
||||||
|
findEmpty--;
|
||||||
|
// This is slightly more redundant than the source, but I trust the compiler to add a branch here
|
||||||
|
if (findEmpty < 0x100) {
|
||||||
|
setUpDictionary(pCodes, pTk, findEmpty);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// We decrease the index and check the entry until we find an empty entry or the end of the table
|
||||||
|
} while ((((pCodes[findEmpty] & kMaskMSBS) | pTk[findEmpty]) & kMaskLSBS) != 0);
|
||||||
|
|
||||||
|
// The link is zero, therefor we have found an empty entry
|
||||||
|
|
||||||
|
pCodes[findEmpty] = codeW;
|
||||||
|
pTk[findEmpty] = k | 0x100; // Marked as used, but still no link because this is the end of the list
|
||||||
|
|
||||||
|
// Now we attach a link to this entry from the previous one in the list
|
||||||
|
uint16 link = findEmpty;
|
||||||
|
|
||||||
|
// Get the link of this entry
|
||||||
|
/* 0000 llll llll llll xba
|
||||||
|
* llll llll 0000 llll & kMaskLSBS
|
||||||
|
* llll llll 0000 0000
|
||||||
|
*/
|
||||||
|
tmp = xba(link) & kMaskLSBS;
|
||||||
|
|
||||||
|
// On the previous entry, take out the K and add the new link onto it
|
||||||
|
/* xxxx xxxx xxxx xxxx & kMaskLow
|
||||||
|
* 0000 0000 xxxx xxxx | tmp
|
||||||
|
* llll llll xxxx xxxx
|
||||||
|
*/
|
||||||
|
pTk[prev] = (pTk[prev] & kMaskLow) | tmp;
|
||||||
|
|
||||||
|
// And now the code gets it's half of the link written in
|
||||||
|
/* 0000 llll llll llll << 4
|
||||||
|
* llll llll llll llll & kMaskMSBS
|
||||||
|
* llll 0000 0000 0000 | pCodes[Prev]
|
||||||
|
* llll xxxx xxxx xxxx
|
||||||
|
*/
|
||||||
|
pCodes[prev] = ((link << 4) & kMaskMSBS) | pCodes[prev];
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Table is full, reset dictionary
|
||||||
|
setUpDictionary(pCodes, pTk, findEmpty);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
hash = xba(ptk[hash]);
|
// Put the link code together by combining the MSBS of the code and the LSBS of k
|
||||||
hash = (hash & kMaskLow) | (tmp >> 4);
|
/* code = l000 >> 4
|
||||||
hash <<= 1;
|
* = 0l00
|
||||||
}
|
* k = ll00 xba
|
||||||
}
|
* = 00ll
|
||||||
return hash;
|
* k | code = 0lll
|
||||||
}
|
*/
|
||||||
|
tmp = (pCodes[index] & kMaskMSBS) >> 4;
|
||||||
void ImmortalEngine::appendList(uint16 codeW, uint16 k, uint16 &hash, uint16 &findEmpty, uint16 start[], uint16 ptk[], uint16 &tmp) {
|
index = ((xba(pTk[index]) & kMaskLow) | tmp);// << 1;
|
||||||
uint16 prev;
|
|
||||||
uint16 link;
|
|
||||||
|
|
||||||
prev = hash;
|
|
||||||
if (hash >= 0x200) {
|
|
||||||
setupDictionary(start, ptk, findEmpty);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
bool found = false;
|
|
||||||
while (found == false) {
|
|
||||||
hash -= 2;
|
|
||||||
if (hash >= 0x200) {
|
|
||||||
setupDictionary(start, ptk, findEmpty);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split up the conditional statement to be easier to follow
|
|
||||||
uint16 cond;
|
|
||||||
cond = start[hash] & kMaskLast;
|
|
||||||
cond |= ptk[hash];
|
|
||||||
|
|
||||||
if ((cond & kMaskHigh) == 0) {
|
|
||||||
findEmpty = hash;
|
|
||||||
start[hash] = codeW;
|
|
||||||
ptk[hash] = k | 0x100;
|
|
||||||
|
|
||||||
link = hash >> 1;
|
|
||||||
|
|
||||||
ptk[prev] = (link << 8) | (ptk[prev] & kMaskLow);
|
|
||||||
start[prev] |= (link >> 4) & kMaskLast;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user