Files
Konrad Beckmann 20d7fefaf9 Import mikmod
2021-08-02 02:19:41 +02:00

483 lines
9.3 KiB
C

/* MikMod sound library (c) 2003-2015 Raphael Assenat and others -
* see AUTHORS file for a complete list.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
/* XPK-SQSH depacker
* Algorithm from the portable decruncher by Bert Jahn (24.12.97)
* Checksum added by Sipos Attila <h430827@stud.u-szeged.hu>
* Rewritten for libxmp by Claudio Matsuoka
* Rewritten for libmikmod by O. Sezer
*
* Copyright (C) 2013 Claudio Matsuoka
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef NO_DEPACKERS
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#include "mikmod_internals.h"
#ifdef SUNOS
extern int fprintf(FILE *, const char *, ...);
#endif
struct io {
UBYTE *src;
UBYTE *dest;
int offs;
int srclen;
};
static const UBYTE ctable[]={
2, 3, 4, 5, 6, 7, 8, 0,
3, 2, 4, 5, 6, 7, 8, 0,
4, 3, 5, 2, 6, 7, 8, 0,
5, 4, 6, 2, 3, 7, 8, 0,
6, 5, 7, 2, 3, 4, 8, 0,
7, 6, 8, 2, 3, 4, 5, 0,
8, 7, 6, 2, 3, 4, 5, 0
};
static UWORD readmem16b(UBYTE *m)
{
ULONG a, b;
a = m[0];
b = m[1];
return (a << 8) | b;
}
static ULONG readmem24b(UBYTE *m)
{
ULONG a, b, c;
a = m[0];
b = m[1];
c = m[2];
return (a << 16) | (b << 8) | c;
}
static UWORD xchecksum(ULONG * ptr, ULONG count)
{
register ULONG sum = 0;
while (count-- > 0) {
sum ^= *ptr++;
}
return (UWORD) (sum ^ (sum >> 16));
}
static int has_bits(struct io *io, int count)
{
return (count <= io->srclen - io->offs);
}
static int get_bits(struct io *io, int count)
{
int r;
if (!has_bits(io, count)) {
return -1;
}
r = readmem24b(io->src + (io->offs >> 3));
r <<= io->offs % 8;
r &= 0xffffff;
r >>= 24 - count;
io->offs += count;
return r;
}
static int get_bits_final(struct io *io, int count)
{
/* Note: has_bits check should be done separately since
* this can return negative values.
*/
int r = readmem24b(io->src + (io->offs >> 3));
r <<= (io->offs % 8) + 8;
r >>= 32 - count;
io->offs += count;
return r;
}
static int copy_data(struct io *io, int d1, int *data, UBYTE *dest_start, UBYTE *dest_end)
{
UBYTE *copy_src;
int r, dest_offset, count, copy_len;
if (get_bits(io, 1) == 0) {
copy_len = get_bits(io, 1) + 2;
} else if (get_bits(io, 1) == 0) {
copy_len = get_bits(io, 1) + 4;
} else if (get_bits(io, 1) == 0) {
copy_len = get_bits(io, 1) + 6;
} else if (get_bits(io, 1) == 0) {
copy_len = get_bits(io, 3) + 8;
} else {
copy_len = get_bits(io, 5) + 16;
}
r = get_bits(io, 1);
if (copy_len < 0 || r < 0) {
return -1;
}
if (r == 0) {
r = get_bits(io, 1);
if (r < 0) {
return -1;
}
if (r == 0) {
count = 8;
dest_offset = 0;
} else {
count = 14;
dest_offset = -0x1100;
}
} else {
count = 12;
dest_offset = -0x100;
}
copy_len -= 3;
if (copy_len >= 0) {
if (copy_len != 0) {
d1--;
}
d1--;
if (d1 < 0) {
d1 = 0;
}
}
copy_len += 2;
r = get_bits(io, count);
if (r < 0) {
return -1;
}
copy_src = io->dest + dest_offset - r - 1;
/* Sanity check */
if (copy_src < dest_start || copy_src + copy_len >= dest_end) {
return -1;
}
do {
//printf("dest=%p src=%p end=%p\n", io->dest, copy_src, dest_end);
*io->dest++ = *copy_src++;
} while (copy_len--);
*data = *(--copy_src);
return d1;
}
static int unsqsh_block(struct io *io, UBYTE *dest_start, UBYTE *dest_end)
{
int r, d1, d2, data, unpack_len, count, old_count;
d1 = d2 = data = old_count = 0;
io->offs = 0;
data = *(io->src++);
*(io->dest++) = data;
do {
r = get_bits(io, 1);
if (r < 0)
return -1;
if (d1 < 8) {
if (r) {
d1 = copy_data(io, d1, &data, dest_start, dest_end);
if (d1 < 0)
return -1;
d2 -= d2 >> 3;
continue;
}
unpack_len = 0;
count = 8;
} else {
if (r) {
count = 8;
if (count == old_count) {
if (d2 >= 20) {
unpack_len = 1;
d2 += 8;
} else {
unpack_len = 0;
}
} else {
count = old_count;
unpack_len = 4;
d2 += 8;
}
} else {
r = get_bits(io, 1);
if (r < 0)
return -1;
if (r == 0) {
d1 = copy_data(io, d1, &data, dest_start, dest_end);
if (d1 < 0)
return -1;
d2 -= d2 >> 3;
continue;
}
r = get_bits(io, 1);
if (r < 0)
return -1;
if (r == 0) {
count = 2;
} else {
r = get_bits(io, 1);
if (r < 0)
return -1;
if (r) {
io->offs--;
count = get_bits(io, 3);
if (count < 0)
return -1;
} else {
count = 3;
}
}
count = ctable[8 * old_count + count - 17];
if (count != 8) {
unpack_len = 4;
d2 += 8;
} else {
if (d2 >= 20) {
unpack_len = 1;
d2 += 8;
} else {
unpack_len = 0;
}
}
}
}
if (!has_bits(io, count * (unpack_len + 2))) {
return -1;
}
do {
data -= get_bits_final(io, count);
*io->dest++ = data;
} while (unpack_len--);
if (d1 != 31) {
d1++;
}
old_count = count;
d2 -= d2 >> 3;
} while (io->dest < dest_end);
return 0;
}
static int unsqsh(UBYTE *src, SLONG srclen, UBYTE *dest, SLONG destlen)
{
SLONG len = destlen;
SLONG decrunched = 0;
UBYTE type;
SLONG packed_size, unpacked_size;
ULONG sum, lchk;
UBYTE *c, *dest_start, *dest_end;
UBYTE bc[3];
struct io io;
io.src = src;
io.dest = dest;
dest_start = io.dest;
c = src + 20;
while (len) {
/* Sanity check */
if (c >= src + srclen) {
return -1;
}
type = *c++;
c++; /* hchk */
sum = *(UWORD *)c;
c += 2; /* checksum */
packed_size = readmem16b(c); /* packed */
c += 2;
unpacked_size = readmem16b(c); /* unpacked */
c += 2;
/* Sanity check */
if (packed_size <= 0 || unpacked_size <= 0) {
return -1;
}
if (c + packed_size + 3 > src + srclen) {
return -1;
}
io.src = c + 2;
io.srclen = packed_size << 3;
memcpy(bc, c + packed_size, 3);
memset(c + packed_size, 0, 3);
lchk = xchecksum((ULONG *) (c), (packed_size + 3) >> 2);
memcpy(c + packed_size, bc, 3);
if (lchk != sum) {
return decrunched;
}
if (type == 0) {
/* verbatim block */
decrunched += packed_size;
if (decrunched > destlen) {
return -1;
}
memcpy(io.dest, c, packed_size);
io.dest += packed_size;
c += packed_size;
len -= packed_size;
continue;
}
if (type != 1) {
/* unknown type */
return decrunched;
}
len -= unpacked_size;
decrunched += unpacked_size;
/* Sanity check */
if (decrunched > destlen) {
return -1;
}
packed_size = (packed_size + 3) & 0xfffc;
c += packed_size;
dest_end = io.dest + unpacked_size;
if (unsqsh_block(&io, dest_start, dest_end) < 0) {
return -1;
}
io.dest = dest_end;
}
return decrunched;
}
BOOL XPK_Unpack(MREADER *reader, void **out, long *outlen)
{
UBYTE *src, *dest;
SLONG inlen, srclen, destlen;
_mm_fseek(reader,0,SEEK_END);
inlen = _mm_ftell(reader);
if (inlen <= 8 || inlen > 0x100000)
return 0;
_mm_rewind(reader);
if (_mm_read_M_ULONG(reader) != 0x58504b46) /* XPKF */
return 0;
srclen = _mm_read_M_SLONG(reader);
if (srclen <= 8 || srclen > 0x100000 || srclen > inlen-8)
return 0;
if (_mm_read_M_ULONG(reader) != 0x53515348) /* SQSH */
return 0;
destlen = _mm_read_M_SLONG(reader);
if (destlen < 0 || destlen > 0x200000)
return 0;
if ((src = (UBYTE*) MikMod_malloc(srclen + 3)) == NULL)
return 0;
if ((dest = (UBYTE*) MikMod_malloc(destlen + 100)) == NULL) {
MikMod_free(src);
return 0;
}
if (!_mm_read_UBYTES(src, srclen - 8, reader))
goto err;
if (unsqsh(src, srclen, dest, destlen) != destlen)
goto err;
*out = dest;
*outlen = destlen;
MikMod_free(src);
return 1;
err:
MikMod_free(dest);
MikMod_free(src);
return 0;
}
#endif /* NO_DEPACKERS */