xmpnx/libxmp/src/depackers/s404_dec.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
};