Files
Meep-Eep 325a882c5d SC1 unpacking tools
git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2767 8092fc87-c524-0410-9efc-e669fe64eaf9
2007-06-01 14:01:14 +00:00

337 lines
7.8 KiB
C

// Reverse engineered from the Star Control executable.
#include "lztfb.h"
#include "../../shared/cbytesex.h"
#include "../../shared/util.h"
#include "getbit.h"
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool LZTFB_processMore(LZTFB *lztfb);
LZTFB *
LZTFB_new(const char *buf, size_t len) {
LZTFB *result = NULL;
if (len < 6)
goto err;
if (buf[0] != 0x06) {
// Not a compressed file.
goto err;
}
result = malloc(sizeof (LZTFB));
result->huffTable[0] = NULL;
result->huffTable[1] = NULL;
result->huffTable[2] = NULL;
result->state = 0;
result->stateData.offset = 0;
result->stateData.count = 0;
result->compressedSize = len;
result->uncompressedSize = getU32BE(buf + 2);
if ((buf[1] & 2) != 0) {
result->table2Shift = 7;
} else
result->table2Shift = 6;
BSC_init(&result->bsc, buf + 6, len);
if ((buf[1] & 4) != 0) {
// Literal bytes are huffman encoded.
result->lengthBias = 3;
result->literalsEncoded = true;
} else {
// Literal bytes are read directly from the input.
result->lengthBias = 2;
result->literalsEncoded = false;
result->huffTable[0] = NULL;
}
memset(result->buf, '\0', LZTFB_BUF_SIZE);
result->bufPtr = result->buf;
result->bufFill = 0;
result->lastBufFill = 0;
return result;
err:
if (result != NULL)
LZTFB_delete(result);
return NULL;
}
void
LZTFB_delete(LZTFB *lztfb) {
for (int tableI = 0; tableI < 3; tableI++) {
if (lztfb->huffTable[0] != NULL)
Huff_delete(lztfb->huffTable[0]);
}
free(lztfb);
}
int
LZTFB_output(LZTFB *lztfb, FILE *out) {
// 21e0:00aa
size_t toOutput = lztfb->uncompressedSize;
while (toOutput > 0) {
lztfb->bufPtr = lztfb->buf;
lztfb->bufFill = 0;
if (!LZTFB_processMore(lztfb)) {
logError(false, "LZTFB_processMore failed.\n");
errno = EIO;
return -1;
}
lztfb->lastBufFill = lztfb->bufFill;
size_t toWrite = lztfb->bufFill;
if (toWrite > toOutput)
toWrite = toOutput;
size_t written = fwrite(lztfb->buf, 1, toWrite, out);
if (written != toWrite) {
assert(ferror(out));
logError(true, "fwrite() failed.\n");
return -1;
}
toOutput -= written;
}
return 0;
}
// Memcopy from left to right. (memcpy doesn't guarantee this, and memmove
// does it differently)
static inline void
memcpyLtr(void *dest, void *src, size_t size) {
while (size--) {
*((uint8_t *) dest) = *((uint8_t *) src);
src = ((uint8_t *) src) + 1;
dest = ((uint8_t *) dest) + 1;
}
}
static bool
LZTFB_processMore(LZTFB *lztfb) {
// 2203:0555
int32_t offset = lztfb->stateData.offset;
uint32_t count = lztfb->stateData.count;
lztfb->stateData.count = 0;
uint8_t table2Shift = lztfb->table2Shift;
uint8_t lengthBias = lztfb->lengthBias;
if (lztfb->state == 0) {
if (lztfb->literalsEncoded) {
lztfb->huffTable[0] = Huff_new(&lztfb->bsc, 0x100);
if (lztfb->huffTable[0] == NULL)
goto err;
}
lztfb->huffTable[1] = Huff_new(&lztfb->bsc, 0x40);
if (lztfb->huffTable[1] == NULL)
goto err;
lztfb->huffTable[2] = Huff_new(&lztfb->bsc, 0x40);
if (lztfb->huffTable[2] == NULL)
goto err;
goto state1;
}
if (lztfb->state == 1) {
state1:
// State 1: read more data and decide what to do.
// The original code flagged EOF when it needed to read a new
// byte, and this wasn't possible. But while EOF it would
// still return undefined bytes.
// Because there may be a couple of bits left at the end of the
// stream (as the stream contains bytes) we can't know whether
// the end of the stream has been reached as long as we still
// have a couple of bits left. So we handle EOF the same way.
if (lztfb->bsc.eof)
return true;
int bit = getBit(&lztfb->bsc);
if (bit != 0) {
// Literal byte.
if (lztfb->huffTable[0] == NULL) {
// Literal byte is read directly from the input.
*lztfb->bufPtr = getBits(&lztfb->bsc, 8);
} else {
// Literal byte is Huffman-encoded.
uint32_t dummy;
if (!Huff_getCode(lztfb->huffTable[0], &dummy))
goto err;
*lztfb->bufPtr = (uint8_t) dummy;
}
lztfb->bufPtr++;
lztfb->bufFill++;
if (lztfb->bufFill == LZTFB_BUF_SIZE) {
lztfb->state = 1;
return true;
}
goto state1;
}
offset = getBits(&lztfb->bsc, table2Shift);
uint32_t ax;
if (!Huff_getCode(lztfb->huffTable[2], &ax))
goto err;
ax = (ax << table2Shift) | offset;
offset = lztfb->lastBufFill + lztfb->bufFill - ax;
// This looks like a bug; if lztfb->bufFill !=
// LZTFB_BUF_SIZE, the offset is wrong.
if (!Huff_getCode(lztfb->huffTable[1], &ax))
goto err;
ax += lengthBias;
count = ax;
if (ax == lengthBias + 0x3fU) {
// 6 bits was not enough; read 8 more.
count += getBits(&lztfb->bsc, 8);
}
offset--;
if (offset < 0) {
goto state2;
} else
goto state3;
}
if (lztfb->state == 2) {
state2:
// State 2: repeat '\0' a number of times.
// offset == -repeatCount
// 2203:0588
// Never more than count bytes.
if (offset < (int32_t) -count)
offset = -count;
// This means count will become 0 in the next line.
count += offset;
// offset is negative; count gets smaller
do {
uint32_t repeatCount = -offset;
if (repeatCount + lztfb->bufFill <= LZTFB_BUF_SIZE) {
// Enough room for repeatCount more characters.
memset(lztfb->bufPtr, '\0', repeatCount);
lztfb->bufFill += repeatCount;
lztfb->bufPtr += repeatCount;
offset = 0;
break;
}
lztfb->stateData.count = repeatCount -
(LZTFB_BUF_SIZE - lztfb->bufFill);
offset += (LZTFB_BUF_SIZE - lztfb->bufFill);
// offset was negative; it now contains the negation of
// the number of bytes that still fit in the buffer.
} while (offset != 0);
if (lztfb->bufFill != LZTFB_BUF_SIZE) {
if (count == 0)
goto state1;
// count bytes will be copied from the start of the buffer.
// I don't get it.
goto state3;
}
// Buffer is full
if (count != 0 || lztfb->stateData.count != 0) {
lztfb->stateData.offset = -lztfb->stateData.count;
lztfb->stateData.count += count;
lztfb->state = 2;
// Next time, continue where we left off.
return true;
}
lztfb->state = 1;
// Next time, start by reading more data.
return true;
}
state3:
// State 3: Backreference to an earlier piece of data
// offset is the offset of the earlier data
// count is the length.
offset &= LZTFB_BUF_SIZE - 1;
// offset %= LZTFB_BUF_SIZE
for (;;) {
if (count + lztfb->bufFill > LZTFB_BUF_SIZE) {
// There's no room in the buffer for this many bytes. Adjust
// the size, and save the rest for later.
lztfb->stateData.count =
count - (LZTFB_BUF_SIZE - lztfb->bufFill);
count = LZTFB_BUF_SIZE - lztfb->bufFill;
if (count == 0) {
// Buffer was completely full.
break;
}
}
if (count + offset <= LZTFB_BUF_SIZE) {
// All the data that is referenced comes from the previous
// call to this function.
// Copy lztfb[offset..(offset + count)] to bufPtr.
memcpyLtr(lztfb->bufPtr, &lztfb->buf[offset], count);
lztfb->bufPtr += count;
lztfb->bufFill += count;
break;
}
// Copy lztfb[offset..] to bufPtr. The rest of the data to be copied
// comes from the data we wrote this call.
memcpyLtr(lztfb->bufPtr, &lztfb->buf[offset],
LZTFB_BUF_SIZE - offset);
lztfb->bufPtr += LZTFB_BUF_SIZE - offset;
lztfb->bufFill += LZTFB_BUF_SIZE - offset;
count -= LZTFB_BUF_SIZE - offset;
offset = 0;
}
// count contains the number of bytes written since offset was last
// adjusted.
if (lztfb->bufFill != LZTFB_BUF_SIZE)
goto state1;
if (lztfb->stateData.count == 0) {
lztfb->state = 1;
// Next time, start by reading new data.
return true;
} else {
lztfb->stateData.offset = offset + count;
// offset was not yet adjusted with count after the last write
lztfb->state = 3;
// Next time, continue where we left off.
return true;
}
err:
return false;
}