433 lines
9.3 KiB
C
433 lines
9.3 KiB
C
/*
|
|
StoneCracker S404 algorithm data decompression routine
|
|
(c) 2006 Jouni 'Mr.Spiv' Korhonen. The code is in public domain.
|
|
|
|
from shd:
|
|
Some portability notes. We are using int32_t as a file size, and that fits
|
|
all Amiga file sizes. size_t is of course the right choice.
|
|
|
|
Warning: Code is not re-entrant.
|
|
|
|
modified for xmp by Claudio Matsuoka, Jan 2010
|
|
(couldn't keep stdint types, some platforms we build on didn't like them)
|
|
*/
|
|
|
|
/*#include <assert.h>*/
|
|
#include "../common.h"
|
|
#include "depacker.h"
|
|
|
|
|
|
struct bitstream {
|
|
/* bit buffer for rolling data bit by bit from the compressed file */
|
|
uint32 word;
|
|
|
|
/* bits left in the bit buffer */
|
|
int left;
|
|
|
|
/* compressed data source */
|
|
uint16 *src;
|
|
uint8 *orgsrc;
|
|
};
|
|
|
|
|
|
static int initGetb(struct bitstream *bs, uint8 *src, uint32 src_length)
|
|
{
|
|
int eff;
|
|
|
|
bs->src = (uint16 *) (src + src_length);
|
|
bs->orgsrc = src;
|
|
|
|
bs->left = readmem16b((uint8 *)bs->src); /* bit counter */
|
|
/*if (bs->left & (~0xf))
|
|
fprintf(stderr, "Workarounded an ancient stc bug\n");*/
|
|
/* mask off any corrupt bits */
|
|
bs->left &= 0x000f;
|
|
bs->src--;
|
|
|
|
/* get the first 16-bits of the compressed stream */
|
|
bs->word = readmem16b((uint8 *)bs->src);
|
|
bs->src--;
|
|
|
|
eff = readmem16b((uint8 *)bs->src); /* efficiency */
|
|
bs->src--;
|
|
|
|
return eff;
|
|
}
|
|
|
|
|
|
/* get nbits from the compressed stream */
|
|
static int getb(struct bitstream *bs, int nbits)
|
|
{
|
|
bs->word &= 0x0000ffff;
|
|
|
|
/* If not enough bits in the bit buffer, get more */
|
|
if (bs->left < nbits) {
|
|
bs->word <<= bs->left;
|
|
/* assert((bs->word & 0x0000ffffU) == 0); */
|
|
|
|
/* Check that we don't go out of bounds */
|
|
/*assert((uint8 *)bs->src >= bs->orgsrc);*/
|
|
if (bs->orgsrc > (uint8 *)bs->src) {
|
|
return -1;
|
|
}
|
|
|
|
bs->word |= readmem16b((uint8 *)bs->src);
|
|
bs->src--;
|
|
|
|
nbits -= bs->left;
|
|
bs->left = 16; /* 16 unused (and some used) bits left in the word */
|
|
}
|
|
|
|
/* Shift nbits off the word and return them */
|
|
bs->left -= nbits;
|
|
bs->word <<= nbits;
|
|
return bs->word >> 16;
|
|
}
|
|
|
|
|
|
/* Returns bytes still to read.. or < 0 if error. */
|
|
static int checkS404File(uint32 *buf,
|
|
int32 *oLen, int32 *pLen, int32 *sLen )
|
|
{
|
|
if (memcmp(buf, "S404", 4) != 0)
|
|
return -1;
|
|
|
|
*sLen = readmem32b((uint8 *)&buf[1]); /* Security length */
|
|
if (*sLen < 0)
|
|
return -1;
|
|
*oLen = readmem32b((uint8 *)&buf[2]); /* Depacked length */
|
|
if (*oLen <= 0)
|
|
return -1;
|
|
*pLen = readmem32b((uint8 *)&buf[3]); /* Packed length */
|
|
if (*pLen <= 6)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int decompressS404(uint8 *src, uint8 *orgdst,
|
|
int32 dst_length, int32 src_length)
|
|
{
|
|
uint16 w;
|
|
int32 eff;
|
|
int32 n;
|
|
uint8 *dst;
|
|
int32 oLen = dst_length;
|
|
struct bitstream bs;
|
|
int x;
|
|
|
|
dst = orgdst + oLen;
|
|
|
|
eff = initGetb(&bs, src, src_length);
|
|
|
|
/* Sanity check--prevent invalid shift exponents. */
|
|
if (eff < 6 || eff >= 32)
|
|
return -1;
|
|
|
|
/*printf("_bl: %02X, _bb: %04X, eff: %d\n",_bl,_bb, eff);*/
|
|
|
|
while (oLen > 0) {
|
|
x = getb(&bs, 9);
|
|
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
|
|
w = x;
|
|
|
|
/*printf("oLen: %d _bl: %02X, _bb: %04X, w: %04X\n",oLen,_bl,_bb,w);*/
|
|
|
|
if (w < 0x100) {
|
|
/*assert(dst > orgdst);*/
|
|
if (orgdst >= dst) {
|
|
return -1;
|
|
}
|
|
*--dst = w;
|
|
/*printf("0+[8] -> %02X\n",w);*/
|
|
oLen--;
|
|
} else if (w == 0x13e || w == 0x13f) {
|
|
w <<= 4;
|
|
x = getb(&bs, 4);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
|
|
n = (w & 0x1f) + 14;
|
|
oLen -= n;
|
|
while (n-- > 0) {
|
|
x = getb(&bs, 8);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w = x;
|
|
|
|
/*printf("1+001+1111+[4] -> [8] -> %02X\n",w);*/
|
|
/*assert(dst > orgdst);*/
|
|
if (orgdst >= dst) {
|
|
return -1;
|
|
}
|
|
|
|
*--dst = w;
|
|
}
|
|
} else {
|
|
if (w >= 0x180) {
|
|
/* copy 2-3 */
|
|
n = w & 0x40 ? 3 : 2;
|
|
|
|
if (w & 0x20) {
|
|
/* dist 545 -> */
|
|
w = (w & 0x1f) << (eff - 5);
|
|
x = getb(&bs, eff - 5);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
w += 544;
|
|
/* printf("1+1+[1]+1+[%d] -> ", eff); */
|
|
} else if (w & 0x30) {
|
|
// dist 1 -> 32
|
|
w = (w & 0x0f) << 1;
|
|
x = getb(&bs, 1);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
/* printf("1+1+[1]+01+[5] %d %02X %d %04X-> ",n,w, _bl, _bb); */
|
|
} else {
|
|
/* dist 33 -> 544 */
|
|
w = (w & 0x0f) << 5;
|
|
x = getb(&bs, 5);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
w += 32;
|
|
/* printf("1+1+[1]+00+[9] -> "); */
|
|
}
|
|
} else if (w >= 0x140) {
|
|
/* copy 4-7 */
|
|
n = ((w & 0x30) >> 4) + 4;
|
|
|
|
if (w & 0x08) {
|
|
/* dist 545 -> */
|
|
w = (w & 0x07) << (eff - 3);
|
|
x = getb(&bs, eff - 3);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
w += 544;
|
|
/* printf("1+01+[2]+1+[%d] -> ", eff); */
|
|
} else if (w & 0x0c) {
|
|
/* dist 1 -> 32 */
|
|
w = (w & 0x03) << 3;
|
|
x = getb(&bs, 3);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
/* printf("1+01+[2]+01+[5] -> "); */
|
|
} else {
|
|
/* dist 33 -> 544 */
|
|
w = (w & 0x03) << 7;
|
|
x = getb(&bs, 7);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
w += 32;
|
|
/* printf("1+01+[2]+00+[9] -> "); */
|
|
}
|
|
} else if (w >= 0x120) {
|
|
/* copy 8-22 */
|
|
n = ((w & 0x1e) >> 1) + 8;
|
|
|
|
if (w & 0x01) {
|
|
/* dist 545 -> */
|
|
x = getb(&bs, eff);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w = x;
|
|
w += 544;
|
|
/* printf("1+001+[4]+1+[%d] -> ", eff); */
|
|
} else {
|
|
x = getb(&bs, 6);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w = x;
|
|
|
|
if (w & 0x20) {
|
|
/* dist 1 -> 32 */
|
|
w &= 0x1f;
|
|
/* printf("1+001+[4]+001+[5] -> "); */
|
|
} else {
|
|
/* dist 33 -> 544 */
|
|
w <<= 4;
|
|
x = getb(&bs, 4);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
|
|
w += 32;
|
|
/* printf("1+001+[4]+00+[9] -> "); */
|
|
}
|
|
}
|
|
} else {
|
|
w = (w & 0x1f) << 3;
|
|
x = getb(&bs, 3);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
n = 23;
|
|
|
|
while (w == 0xff) {
|
|
n += w;
|
|
x = getb(&bs, 8);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w = x;
|
|
}
|
|
n += w;
|
|
|
|
x = getb(&bs, 7);
|
|
w = x;
|
|
|
|
if (w & 0x40) {
|
|
/* dist 545 -> */
|
|
w = (w & 0x3f) << (eff - 6);
|
|
x = getb(&bs, eff - 6);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
|
|
w += 544;
|
|
} else if (w & 0x20) {
|
|
/* dist 1 -> 32 */
|
|
w &= 0x1f;
|
|
/* printf("1+000+[8]+01+[5] -> "); */
|
|
} else {
|
|
/* dist 33 -> 544; */
|
|
w <<= 4;
|
|
x = getb(&bs, 4);
|
|
/* Sanity check */
|
|
if (x < 0) {
|
|
return -1;
|
|
}
|
|
w |= x;
|
|
|
|
w += 32;
|
|
/* printf("1+000+[8]+00+[9] -> "); */
|
|
}
|
|
}
|
|
|
|
/* printf("<%d,%d>\n",n,w+1); fflush(stdout); */
|
|
oLen -= n;
|
|
|
|
while (n-- > 0) {
|
|
/* printf("Copying: %02X\n",dst[w]); */
|
|
dst--;
|
|
if (dst < orgdst || (dst + w + 1) >= (orgdst + dst_length))
|
|
return -1;
|
|
*dst = dst[w + 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_s404(unsigned char *b)
|
|
{
|
|
return memcmp(b, "S404", 4) == 0;
|
|
}
|
|
|
|
static int decrunch_s404(HIO_HANDLE *in, void **out, long inlen, long *outlen)
|
|
{
|
|
int32 oLen, sLen, pLen;
|
|
uint8 *dst = NULL;
|
|
uint8 *buf, *src;
|
|
|
|
if (inlen <= 16)
|
|
return -1;
|
|
src = buf = (uint8 *) malloc(inlen);
|
|
if (src == NULL)
|
|
return -1;
|
|
if (hio_read(buf, 1, inlen, in) != inlen) {
|
|
goto error;
|
|
}
|
|
|
|
if (checkS404File((uint32 *) src, &oLen, &pLen, &sLen)) {
|
|
/*fprintf(stderr,"S404 Error: checkS404File() failed..\n");*/
|
|
goto error;
|
|
}
|
|
|
|
/* Sanity check */
|
|
if (pLen > inlen - 18) {
|
|
goto error;
|
|
}
|
|
|
|
/**
|
|
* Best case ratio of S404 sliding window:
|
|
*
|
|
* 2-3: 9b + (>=1b) -> 2-3B -> 24:10
|
|
* 4-7: 9b + (>=3b) -> 4-7B -> 56:12
|
|
* 8:22: 9b + (>=6b) -> 8-22B -> 176:15
|
|
* 23+: 9b + 3b + 8b * floor((n-23)/255) + 7b + (>=0b) -> n B -> ~255:1
|
|
*/
|
|
if (pLen < (oLen / 255)) {
|
|
goto error;
|
|
}
|
|
|
|
if ((dst = (uint8 *)malloc(oLen)) == NULL) {
|
|
/*fprintf(stderr,"S404 Error: malloc(%d) failed..\n", oLen);*/
|
|
goto error;
|
|
}
|
|
|
|
/* src + 16 skips S404 header */
|
|
if (decompressS404(src + 16, dst, oLen, pLen) < 0) {
|
|
goto error1;
|
|
}
|
|
|
|
free(src);
|
|
|
|
*out = dst;
|
|
*outlen = oLen;
|
|
|
|
return 0;
|
|
|
|
error1:
|
|
free(dst);
|
|
error:
|
|
free(src);
|
|
return -1;
|
|
}
|
|
|
|
struct depacker libxmp_depacker_s404 = {
|
|
test_s404,
|
|
NULL,
|
|
decrunch_s404
|
|
};
|