Add SPDX-License-Identifier to every source file in lib/ and cli/. Files derived from Bobrowski's libunuc2 retain LGPL-3.0-only; cli/src/main.c (derived from his GPL-licensed unuc2 tool) and all new Phase 2-7 work by Valetov are GPL-3.0-or-later. No silent LGPL-to-GPL upgrade has been applied. CREDITS.md now lists each Bobrowski-derived file specifically rather than crediting libunuc2 as generic 'inspiration'. docs/license-audit.md records the full per-file provenance table, the LGPL-3.0 -> GPL-3.0 chain rationale (LGPL sec. 4 Combined Works is the operative clause; LGPL sec. 3 single-direction upgrade is documented but not exercised), and confirms that: - the 2015 LGPL-3.0 release in original/UC2_source/ is preserved unchanged; - the 2020-2021 LGPL/GPL releases in original/unuc2-0.6/ are preserved unchanged; - lib/src/super.bin is bit-identical to upstream and to de Vries's 1992 distribution data.
1640 lines
37 KiB
C
1640 lines
37 KiB
C
/* SPDX-License-Identifier: LGPL-3.0-only */
|
|
|
|
/* UltraCompressor II decompression library.
|
|
Copyright © Jan Bobrowski 2020, 2021
|
|
torinak.com/~jb/unuc2/
|
|
|
|
This program is free software; you can redistribute it and
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License version 3 as published by the Free Software Foundation.
|
|
|
|
Original source by Nico de Vries, AIP used as a reference.
|
|
nicodevries.com/professional/
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
#include "uc2/libuc2.h"
|
|
#include "uc2/uc2_rans.h"
|
|
|
|
#if !defined NDEBUG && !defined NDIAG
|
|
#include <stdio.h>
|
|
static int midl;
|
|
static void diag(char *fnm, int lin, char *fmt, ...)
|
|
{
|
|
if (!midl)
|
|
fprintf(stdout, "%s:%d: ", fnm, lin);
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
midl = fmt[strlen(fmt)-1] != '\n';
|
|
}
|
|
#define diag(...) diag(__FILE__,__LINE__,__VA_ARGS__)
|
|
#else
|
|
static inline void diag(char *f, ...) {}
|
|
#endif
|
|
|
|
typedef uint8_t u8;
|
|
typedef uint16_t u16;
|
|
typedef uint32_t u32;
|
|
|
|
/* records */
|
|
|
|
typedef struct u16le {u8 b[2];} u16le;
|
|
typedef struct u32le {u8 b[4];} u32le;
|
|
|
|
static u16 get16(u16le v) {return v.b[0] | v.b[1]<<8;}
|
|
static u32 get32(u32le v) {return v.b[0] | v.b[1]<<8 | v.b[2]<<16 | (u32)v.b[3]<<24;}
|
|
|
|
#define REC(R) struct R
|
|
|
|
REC(FHEAD) {
|
|
u32le head; // UC2^Z
|
|
u32le componentLength; // length of component contents
|
|
u32le componentLength2;
|
|
u8 damageProtected;
|
|
};
|
|
|
|
REC(LOCATION) {
|
|
u32le volume;
|
|
u32le offset;
|
|
};
|
|
|
|
REC(XHEAD) {
|
|
REC(LOCATION) cdir;
|
|
u16le fletch;
|
|
u8 busy;
|
|
u16le versionMadeBy; // e.g. 200 means 2.00
|
|
u16le versionNeededToExtract;
|
|
u8 dummy;
|
|
};
|
|
|
|
REC(COMPRESS) {
|
|
u32le compressedLength;
|
|
u16le method;
|
|
u32le masterPrefix;
|
|
};
|
|
|
|
REC(OHEAD) {
|
|
u8 type;
|
|
};
|
|
|
|
enum {
|
|
DirEntry = 1,
|
|
FileEntry = 2,
|
|
MasterEntry = 3,
|
|
EndOfCdir = 4
|
|
};
|
|
|
|
REC(OSMETA) {
|
|
u32le parent; // parent directory index
|
|
u8 attrib; // file attributes (MSDOS)
|
|
u32le time; // time last modified (MSDOS)
|
|
u8 name[11]; // MS-DOS compatible name
|
|
u8 hidden; // 0 = plain visual, 1 = completely hidden
|
|
u8 tag; // has tags?
|
|
};
|
|
|
|
REC(FILEMETA) {
|
|
u32le length; // file length
|
|
u16le fletch; // fletcher checksum of raw data
|
|
};
|
|
|
|
REC(DIRMETA) {
|
|
u32le index; // directory index for referencing
|
|
};
|
|
|
|
REC(EXTMETA) {
|
|
u8 tag[16]; // zero terminated
|
|
u32le size; // size of object
|
|
u8 next; // more tags?
|
|
};
|
|
|
|
#define TAG_LONGNAME "AIP:Win95 LongN"
|
|
|
|
REC(MASMETA) {
|
|
u32le index; // master index
|
|
u32le key; // master hash key
|
|
u32le refLen; // total size of refering data
|
|
u32le refCtr; // total number of refering files
|
|
u16le length; // master length
|
|
u16le fletch; // (Garbage 0xDEDE) fletcher checksum of raw data
|
|
};
|
|
|
|
REC(XTAIL) {
|
|
u8 beta; // archive made with beta test version
|
|
u8 lock; // locked archive
|
|
u32le serial; // special serial number (0 = none)
|
|
u8 label[11]; // MS-DOS volume label
|
|
};
|
|
|
|
#include "list.h"
|
|
#define elemof(T) (sizeof T/sizeof*T)
|
|
#define endof(T) (T+elemof(T))
|
|
|
|
int uc2_identify(void *magic, unsigned magic_size)
|
|
{
|
|
struct {
|
|
REC(FHEAD) fhead;
|
|
REC(XHEAD) xhead;
|
|
} *h = magic;
|
|
|
|
#define NEED(M) ((u8*)&h->M - (u8*)h + sizeof h->M)
|
|
|
|
if (magic_size < NEED(fhead.head))
|
|
return -1;
|
|
|
|
const u32 MAGIC = 0x1a324355;
|
|
if (get32(h->fhead.head) != MAGIC)
|
|
return 0;
|
|
|
|
if (magic_size < NEED(fhead.componentLength2))
|
|
return 1;
|
|
|
|
const u32 AMAG = 0x01b2c3d4;
|
|
u32 len = get32(h->fhead.componentLength);
|
|
if (len != (u32)(get32(h->fhead.componentLength2) - AMAG))
|
|
return 0;
|
|
len += sizeof h->fhead;
|
|
|
|
if (magic_size < NEED(xhead.cdir))
|
|
return 1;
|
|
|
|
if (get32(h->xhead.cdir.volume) != 1)
|
|
return 0;
|
|
|
|
if (get32(h->xhead.cdir.offset) >= len)
|
|
return 0;
|
|
#undef NEED
|
|
return 1;
|
|
}
|
|
|
|
struct range {
|
|
u8 *ptr, *end;
|
|
};
|
|
|
|
static unsigned range_len(struct range *r) {return (unsigned)(r->end - r->ptr);}
|
|
|
|
struct uc2_context {
|
|
char *message;
|
|
struct uc2_io *io;
|
|
void *io_ctx;
|
|
|
|
u8 *supermaster;
|
|
struct list masters;
|
|
|
|
u8 *cdir_buf;
|
|
struct range cdir_range;
|
|
|
|
enum {
|
|
Start,
|
|
AtEntry,
|
|
AtTag,
|
|
AtTail
|
|
} cdir_state;
|
|
|
|
u8 scanned:1;
|
|
u8 pcp:1;
|
|
};
|
|
|
|
/* callback */
|
|
|
|
static int u_read(struct uc2_context *uc2, unsigned pos, void *buf, unsigned len)
|
|
{
|
|
return uc2->io->read(uc2->io_ctx, pos, buf, len);
|
|
}
|
|
|
|
static int u_read_all(struct uc2_context *uc2, unsigned pos, void *buf, unsigned len)
|
|
{
|
|
int r = u_read(uc2, pos, buf, len);
|
|
if (r >= 0 && r != len)
|
|
r = UC2_Truncated;
|
|
return r;
|
|
}
|
|
|
|
static void *u_alloc(struct uc2_context *uc2, unsigned size)
|
|
{
|
|
return uc2->io->alloc(uc2->io_ctx, size);
|
|
}
|
|
|
|
static void *u_free(struct uc2_context *uc2, void *ptr)
|
|
{
|
|
if (ptr)
|
|
uc2->io->free(uc2->io_ctx, ptr);
|
|
return 0;
|
|
}
|
|
|
|
#define u_warn(U, ...) ((U)->io->warn ? ((U)->io->warn((U)->io_ctx, __VA_ARGS__),1) : 0)
|
|
|
|
/* rw */
|
|
|
|
struct reader {
|
|
void *context;
|
|
int (*read)(void *context, void *buffer, unsigned size);
|
|
};
|
|
|
|
struct writer {
|
|
void *context;
|
|
int (*write)(void *context, const void *buffer, unsigned size); // ret: -1 or 0
|
|
};
|
|
|
|
struct archive_ctx {
|
|
unsigned offset;
|
|
struct uc2_context *uc2;
|
|
};
|
|
|
|
static int archive_read(void *context, void *buffer, unsigned size)
|
|
{
|
|
struct archive_ctx *ctx = context;
|
|
int r = u_read(ctx->uc2, ctx->offset, buffer, size);
|
|
if (r > 0)
|
|
ctx->offset += r;
|
|
return r;
|
|
}
|
|
|
|
static int buf_read(void *context, void *ptr, unsigned size)
|
|
{
|
|
struct range *br = context;
|
|
unsigned have = range_len(br);
|
|
if (have < size) {
|
|
if (!have)
|
|
return 0;
|
|
size = have;
|
|
}
|
|
memcpy(ptr, br->ptr, size);
|
|
br->ptr += size;
|
|
return size;
|
|
}
|
|
|
|
static int buf_write(void *context, const void *ptr, unsigned size)
|
|
{
|
|
struct range *bw = context;
|
|
unsigned free = range_len(bw);
|
|
if (free < size) {
|
|
if (free == 0)
|
|
return 0;
|
|
size = free;
|
|
}
|
|
memcpy(bw->ptr, ptr, size);
|
|
bw->ptr += size;
|
|
return 0;
|
|
}
|
|
|
|
struct user_write_ctx {
|
|
int (*write)(void *context, const void *ptr, unsigned len);
|
|
void *context;
|
|
};
|
|
|
|
static int user_write(void *context, const void *buffer, unsigned size)
|
|
{
|
|
struct user_write_ctx *uc = context;
|
|
return uc->write(uc->context, buffer, size) < 0 ? UC2_UserFault : 0;
|
|
}
|
|
|
|
static void *range_get(struct range *r, unsigned n)
|
|
{
|
|
unsigned l = range_len(r);
|
|
if (l < n)
|
|
return 0;
|
|
u8 *p = r->ptr;
|
|
r->ptr += n;
|
|
return p;
|
|
}
|
|
|
|
/* bits */
|
|
|
|
struct bits {
|
|
u32 bits;
|
|
unsigned have_bits;
|
|
unsigned head, tail;
|
|
struct reader *rd;
|
|
u8 buffer[4 << 10];
|
|
};
|
|
|
|
static int bits_init(struct bits *bi, struct reader *rd)
|
|
{
|
|
bi->head = 0;
|
|
bi->tail = 0;
|
|
bi->bits = 0;
|
|
bi->have_bits = 0;
|
|
bi->rd = rd;
|
|
return 0;
|
|
}
|
|
|
|
static void bits_skip(struct bits *bi, unsigned n)
|
|
{
|
|
assert(bi->have_bits >= n);
|
|
bi->have_bits -= n;
|
|
}
|
|
|
|
static int bits_feed(struct bits *bi, unsigned n)
|
|
{
|
|
assert(n <= 16);
|
|
if (bi->have_bits < n) {
|
|
unsigned have = bi->tail - bi->head;
|
|
if (have <= 1) {
|
|
if (have == 1)
|
|
bi->buffer[0] = bi->buffer[bi->tail - 1];
|
|
bi->tail = have;
|
|
int r = bi->rd->read(bi->rd->context, bi->buffer + have, sizeof bi->buffer - have);
|
|
if (r <= 0)
|
|
return r ? r : UC2_Truncated;
|
|
bi->head = 0;
|
|
bi->tail += r;
|
|
}
|
|
bi->bits = bi->bits << 16 | bi->buffer[bi->head] | bi->buffer[bi->head + 1] << 8;
|
|
bi->head += 2;
|
|
bi->have_bits += 16;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bits_peek(struct bits *bi, unsigned n)
|
|
{
|
|
int r = bits_feed(bi, n);
|
|
if (r < 0)
|
|
return r;
|
|
return bi->bits >> (bi->have_bits - n) & ((1 << n) - 1);
|
|
}
|
|
|
|
static int bits_get(struct bits *bi, unsigned n)
|
|
{
|
|
int r = bits_peek(bi, n);
|
|
if (r >= 0) bits_skip(bi, n);
|
|
return r;
|
|
}
|
|
|
|
static void bits_destroy(struct bits *bi) {}
|
|
|
|
/* csum */
|
|
|
|
struct csum {
|
|
u32 value;
|
|
};
|
|
|
|
static void csum_init(struct csum *cs)
|
|
{
|
|
cs->value = 0xA55A;
|
|
}
|
|
|
|
static void csum_update(struct csum *cs, const u8 *p, unsigned n)
|
|
{
|
|
if (!n)
|
|
return;
|
|
u32 v = cs->value;
|
|
const u8 *e = p + n - 1;
|
|
if (v > 0xffff)
|
|
v ^= *p++ << 8;
|
|
while (p < e) {
|
|
v ^= p[0] | p[1]<<8;
|
|
p += 2;
|
|
}
|
|
v &= 0xffff;
|
|
if (p == e)
|
|
v ^= *p | 0x10000;
|
|
cs->value = v;
|
|
}
|
|
|
|
static u16 csum_get(struct csum *cs)
|
|
{
|
|
return (u16)cs->value;
|
|
}
|
|
|
|
/* names */
|
|
|
|
static u16 cp850[] = {
|
|
0x00c7,0x00fc,0x00e9,0x00e2,0x00e4,0x00e0,0x00e5,0x00e7, 0x00ea,0x00eb,0x00e8,0x00ef,0x00ee,0x00ec,0x00c4,0x00c5,
|
|
0x00c9,0x00e6,0x00c6,0x00f4,0x00f6,0x00f2,0x00fb,0x00f9, 0x00ff,0x00d6,0x00dc,0x00f8,0x00a3,0x00d8,0x00d7,0x0192,
|
|
0x00e1,0x00ed,0x00f3,0x00fa,0x00f1,0x00d1,0x00aa,0x00ba, 0x00bf,0x00ae,0x00ac,0x00bd,0x00bc,0x00a1,0x00ab,0x00bb,
|
|
0x2591,0x2592,0x2593,0x2502,0x2524,0x00c1,0x00c2,0x00c0, 0x00a9,0x2563,0x2551,0x2557,0x255d,0x00a2,0x00a5,0x2510,
|
|
0x2514,0x2534,0x252c,0x251c,0x2500,0x253c,0x00e3,0x00c3, 0x255a,0x2554,0x2569,0x2566,0x2560,0x2550,0x256c,0x00a4,
|
|
0x00f0,0x00d0,0x00ca,0x00cb,0x00c8,0x0131,0x00cd,0x00ce, 0x00cf,0x2518,0x250c,0x2588,0x2584,0x00a6,0x00cc,0x2580,
|
|
0x00d3,0x00df,0x00d4,0x00d2,0x00f5,0x00d5,0x00b5,0x00fe, 0x00de,0x00da,0x00db,0x00d9,0x00fd,0x00dd,0x00af,0x00b4,
|
|
0x00ad,0x00b1,0x2017,0x00be,0x00b6,0x00a7,0x00f7,0x00b8, 0x00b0,0x00a8,0x00b7,0x00b9,0x00b3,0x00b2,0x25a0,0x00a0
|
|
};
|
|
|
|
static u16 cp850_tolower[] = {
|
|
0x00e7,0x00fc,0x00e9,0x00e2,0x00e4,0x00e0,0x00e5,0x00e7, 0x00ea,0x00eb,0x00e8,0x00ef,0x00ee,0x00ec,0x00e4,0x00e5,
|
|
0x00e9,0x00e6,0x00e6,0x00f4,0x00f6,0x00f2,0x00fb,0x00f9, 0x00ff,0x00f6,0x00fc,0x00f8,0x00a3,0x00f8,0x00d7,0x0192,
|
|
0x00e1,0x00ed,0x00f3,0x00fa,0x00f1,0x00f1,0x00aa,0x00ba, 0x00bf,0x00ae,0x00ac,0x00bd,0x00bc,0x00a1,0x00ab,0x00bb,
|
|
0x2591,0x2592,0x2593,0x2502,0x2524,0x00e1,0x00e2,0x00e0, 0x00a9,0x2563,0x2551,0x2557,0x255d,0x00a2,0x00a5,0x2510,
|
|
0x2514,0x2534,0x252c,0x251c,0x2500,0x253c,0x00e3,0x00e3, 0x255a,0x2554,0x2569,0x2566,0x2560,0x2550,0x256c,0x00a4,
|
|
0x00f0,0x00f0,0x00ea,0x00eb,0x00e8,0x0131,0x00ed,0x00ee, 0x00ef,0x2518,0x250c,0x2588,0x2584,0x00a6,0x00ec,0x2580,
|
|
0x00f3,0x00df,0x00f4,0x00f2,0x00f5,0x00f5,0x00b5,0x00fe, 0x00fe,0x00fa,0x00fb,0x00f9,0x00fd,0x00fd,0x00af,0x00b4,
|
|
0x00ad,0x00b1,0x2017,0x00be,0x00b6,0x00a7,0x00f7,0x00b8, 0x00b0,0x00a8,0x00b7,0x00b9,0x00b3,0x00b2,0x25a0,0x00a0
|
|
};
|
|
|
|
enum casechg {KeepCase, LowerCase};
|
|
|
|
static u8 *put_utf8(u8 *d, u8 *e, enum casechg cc, u8 c)
|
|
{
|
|
if (c < 128) {
|
|
if (d+1 > e) return 0;
|
|
if (cc == LowerCase && c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
} else {
|
|
if (d+2 > e) return 0;
|
|
u16 u = (cc==LowerCase ? cp850_tolower : cp850)[c - 128];
|
|
c = 0xC0;
|
|
if (u >= 0x800) {
|
|
if (d+3 > e) return 0;
|
|
*d++ = u >> 12 | 0xE0;
|
|
c = 0x80;
|
|
}
|
|
*d++ = (u >> 6 & 0x3F) | c;
|
|
c = (u & 0x3F) | 0x80;
|
|
}
|
|
*d++ = c;
|
|
assert(d <= e);
|
|
return d;
|
|
}
|
|
|
|
static void copy_long_name(struct uc2_entry *e, u8 *s, u8 *se)
|
|
{
|
|
u8 *d = (u8*)e->name;
|
|
u8 *de = d + sizeof e->name - 1;
|
|
do {
|
|
u8 c = *s++;
|
|
if (!c)
|
|
break;
|
|
if (d == de)
|
|
return;
|
|
d = put_utf8(d, de, KeepCase, c);
|
|
if (!d)
|
|
return;
|
|
} while (s < se);
|
|
*d = 0;
|
|
e->name_len = (unsigned short)(d - (u8*)e->name);
|
|
}
|
|
|
|
static void assemble_name(struct uc2_entry *e)
|
|
{
|
|
u8 *d = (u8*)e->name;
|
|
u8 *s = e->dos_name;
|
|
u8 *z = s + 8;
|
|
for (;;) {
|
|
while (z > s) {
|
|
if (z[-1] != ' ')
|
|
break;
|
|
z--;
|
|
}
|
|
if (s > e->dos_name) {
|
|
if (s == z)
|
|
break;
|
|
*d++ = '.';
|
|
}
|
|
while (s < z) {
|
|
u8 c = *s++;
|
|
d = put_utf8(d, (u8*)e->name + sizeof e->name, LowerCase, c);
|
|
assert(d);
|
|
}
|
|
s = e->dos_name + 8;
|
|
if (s < z)
|
|
break;
|
|
z = s + 3;
|
|
}
|
|
*d = 0;
|
|
e->name_len = (unsigned short)(d - (u8*)e->name);
|
|
}
|
|
|
|
/* master */
|
|
|
|
static int decompressor(struct uc2_context *uc2, int method, struct reader *rd, struct writer *wr, unsigned master, unsigned len, u16 *csum);
|
|
|
|
struct compress {
|
|
u32 csize;
|
|
u16 method;
|
|
u32 master;
|
|
};
|
|
|
|
enum {
|
|
SuperMaster = 0,
|
|
NoMaster = 1,
|
|
FirstMaster = 2
|
|
};
|
|
|
|
struct master_info {
|
|
u32 id;
|
|
u16 size;
|
|
unsigned offset;
|
|
struct compress com;
|
|
struct list list;
|
|
struct master_info *needed_by; // in resolve_master()
|
|
u8 *data;
|
|
};
|
|
|
|
static struct master_info *find_master(struct uc2_context *uc2, unsigned id)
|
|
{
|
|
for (struct list *l = uc2->masters.next; l != &uc2->masters; l = l->next) {
|
|
struct master_info *mi = list_item(l, struct master_info, list);
|
|
if (mi->id == id)
|
|
return mi;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int resolve_master(struct uc2_context *uc2, unsigned master)
|
|
{
|
|
if (uc2->pcp) {
|
|
uc2->message = "PCP not implemented";
|
|
return UC2_Unimplemented;
|
|
}
|
|
|
|
if (!uc2->supermaster) {
|
|
uc2->supermaster = u_alloc(uc2, 49152);
|
|
if (!uc2->supermaster)
|
|
return UC2_UserFault;
|
|
|
|
#ifdef _MSC_VER
|
|
extern u8 uc2_supermaster_compressed[];
|
|
extern const unsigned int uc2_supermaster_compressed_size;
|
|
struct range br = {.ptr = uc2_supermaster_compressed, .end = uc2_supermaster_compressed + uc2_supermaster_compressed_size};
|
|
#else
|
|
extern u8 uc2_supermaster_compressed[], uc2_supermaster_compressed_end[];
|
|
struct range br = {.ptr = uc2_supermaster_compressed, .end = uc2_supermaster_compressed_end};
|
|
#endif
|
|
struct range bw = {.ptr = uc2->supermaster, .end = uc2->supermaster + 49152};
|
|
struct reader rd = {.read = buf_read, .context = &br};
|
|
struct writer wr = {.write = buf_write, .context = &bw};
|
|
u16 csum;
|
|
int r = decompressor(uc2, 4, &rd, &wr, NoMaster, 49152, &csum);
|
|
if (r < 0)
|
|
return r;
|
|
if (csum != 0x1E55)
|
|
return UC2_InternalError;
|
|
}
|
|
|
|
if (master < FirstMaster)
|
|
return 0;
|
|
|
|
struct master_info *mi = 0;
|
|
do {
|
|
struct master_info *m = find_master(uc2, master);
|
|
if (!m) {
|
|
diag("Master %X missing\n", master);
|
|
return UC2_Damaged;
|
|
}
|
|
if (m->size > 0xffff)
|
|
return UC2_Damaged;
|
|
if (m->needed_by) {
|
|
diag("%X: Circular dependency\n", master);
|
|
return UC2_Damaged;
|
|
}
|
|
if (m->data)
|
|
break;
|
|
if (mi)
|
|
m->needed_by = mi;
|
|
mi = m;
|
|
master = m->com.master;
|
|
} while (master >= FirstMaster);
|
|
|
|
while (mi) {
|
|
diag("Decompressing master %X size:%u master:%X method:%u\n", mi->id, mi->size, mi->com.master, mi->com.method);
|
|
mi->data = u_alloc(uc2, mi->size);
|
|
if (!mi->data)
|
|
return UC2_UserFault;
|
|
struct archive_ctx ar = {.offset = mi->offset, .uc2 = uc2};
|
|
struct reader rd = {.read = archive_read, .context = &ar};
|
|
struct range bw = {.ptr = mi->data, .end = mi->data + mi->size};
|
|
struct writer wr = {.write = buf_write, .context = &bw};
|
|
int r = decompressor(uc2, mi->com.method, &rd, &wr, mi->com.master, mi->size, 0);
|
|
diag("Decompressed master %u left:%u\n", mi->id, range_len(&bw));
|
|
if (r < 0)
|
|
return r;
|
|
struct master_info *m = mi;
|
|
mi = mi->needed_by;
|
|
m->needed_by = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int use_master(struct uc2_context *uc2, u8 buffer[65535], u32 id)
|
|
{
|
|
int size;
|
|
|
|
switch (id) {
|
|
case SuperMaster:
|
|
diag("Using supermaster\n");
|
|
size = 49152;
|
|
if (buffer)
|
|
memcpy(buffer, uc2->supermaster, size);
|
|
break;
|
|
case NoMaster:
|
|
diag("No master\n");
|
|
size = 512;
|
|
if (buffer)
|
|
memset(buffer, 0, size);
|
|
break;
|
|
default:
|
|
diag("Using master %d\n", id);
|
|
struct master_info *mi = find_master(uc2, id);
|
|
assert(mi); // Wev'e fetched it already
|
|
assert(mi->data);
|
|
assert(mi->size <= 65535);
|
|
size = mi->size;
|
|
if (buffer)
|
|
memcpy(buffer, mi->data, size);
|
|
}
|
|
|
|
diag("Master %d len:%u\n", id, size);
|
|
return size;
|
|
}
|
|
|
|
/* cdir */
|
|
|
|
static int cdir_damaged(struct uc2_context *uc2);
|
|
|
|
static int decompress_cdir(struct uc2_context *uc2, u32 offset, u16 csum)
|
|
{
|
|
assert(!uc2->cdir_buf);
|
|
|
|
REC(COMPRESS) c;
|
|
|
|
int ret = u_read_all(uc2, offset, &c, sizeof c);
|
|
if (ret < 0)
|
|
return ret;
|
|
offset += sizeof c;
|
|
|
|
u32 master = get32(c.masterPrefix);
|
|
if (master != NoMaster)
|
|
return cdir_damaged(uc2);
|
|
|
|
enum {
|
|
Prealloc = 0x4000
|
|
};
|
|
|
|
unsigned size = Prealloc;
|
|
for (;;) {
|
|
uc2->cdir_buf = u_alloc(uc2, size);
|
|
if (!uc2->cdir_buf)
|
|
return UC2_UserFault;
|
|
|
|
struct archive_ctx ar = {.offset = offset, .uc2 = uc2};
|
|
struct reader rd = {.read = archive_read, .context = &ar};
|
|
struct range wrctx = {.ptr = uc2->cdir_buf, .end = uc2->cdir_buf + size};
|
|
struct writer wr = {.write = buf_write, .context = &wrctx};
|
|
u16 cs;
|
|
ret = decompressor(uc2, get16(c.method), &rd, &wr, NoMaster, 100000000, &cs);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (cs != csum)
|
|
return cdir_damaged(uc2);
|
|
|
|
if ((unsigned)ret <= size)
|
|
break;
|
|
|
|
diag("Decompressing Cdir again (size:%u < %d)\n", size, ret);
|
|
size = ret;
|
|
uc2->cdir_buf = u_free(uc2, uc2->cdir_buf);
|
|
}
|
|
|
|
uc2->cdir_range.end = uc2->cdir_buf + size;
|
|
return 0;
|
|
}
|
|
|
|
static int start_read(struct uc2_context *uc2);
|
|
static int read_entry(struct uc2_context *uc2, struct uc2_entry *e, u8 type);
|
|
static void copy_dos_name(u8 *dos_name, u8 *s);
|
|
|
|
int uc2_read_cdir(struct uc2_context *uc2, struct uc2_entry *e)
|
|
{
|
|
int ret;
|
|
|
|
if (uc2->cdir_state == Start) {
|
|
if (!uc2->cdir_buf) {
|
|
ret = start_read(uc2);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
uc2->cdir_range.ptr = uc2->cdir_buf;
|
|
uc2->cdir_state = AtEntry;
|
|
}
|
|
|
|
for (;;) {
|
|
REC(OHEAD) *oh = range_get(&uc2->cdir_range, sizeof *oh);
|
|
if (!oh)
|
|
return UC2_Truncated;
|
|
switch (oh->type) {
|
|
case FileEntry:
|
|
case DirEntry:
|
|
ret = read_entry(uc2, e, oh->type);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret > UC2_BareEntry)
|
|
uc2->cdir_state = AtTag;
|
|
if (e)
|
|
return ret;
|
|
|
|
ret = uc2_get_tag(uc2, 0, 0, 0, 0); // Skip tags
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case MasterEntry:;
|
|
struct {
|
|
REC(MASMETA) m;
|
|
REC(COMPRESS) c;
|
|
REC(LOCATION) l;
|
|
} *m = range_get(&uc2->cdir_range, sizeof *m);
|
|
if (!m)
|
|
return UC2_Truncated;
|
|
if (uc2->scanned)
|
|
break;
|
|
if (get32(m->l.volume) != 1)
|
|
return UC2_Unimplemented;
|
|
|
|
struct master_info *mi = u_alloc(uc2, sizeof *mi);
|
|
if (!mi)
|
|
return UC2_UserFault;
|
|
mi->id = get32(m->m.index);
|
|
mi->size = get16(m->m.length);
|
|
mi->offset = get32(m->l.offset);
|
|
mi->com.csize = get32(m->c.compressedLength);
|
|
mi->com.method = get16(m->c.method);
|
|
mi->com.master = get32(m->c.masterPrefix);
|
|
// assert(get16(m.m.fletch) == 0xdede);
|
|
diag("master %X sz:%u csize:%u loc:%u csum:%04X master:%X\n", mi->id, mi->size, mi->com.csize, mi->offset, get16(m->m.fletch), mi->com.master);
|
|
if (mi->com.master == 0xdededede)
|
|
mi->com.master = SuperMaster;
|
|
mi->needed_by = 0;
|
|
mi->data = 0;
|
|
list_add(&uc2->masters, &mi->list);
|
|
break;
|
|
|
|
case EndOfCdir:
|
|
uc2->cdir_state = AtTail;
|
|
uc2->scanned = 1;
|
|
return UC2_End;
|
|
|
|
default:
|
|
return cdir_damaged(uc2);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int read_entry(struct uc2_context *uc2, struct uc2_entry *e, u8 type)
|
|
{
|
|
struct {
|
|
REC(OSMETA) m;
|
|
union {
|
|
struct {
|
|
REC(FILEMETA) m;
|
|
REC(COMPRESS) c;
|
|
REC(LOCATION) l;
|
|
} f;
|
|
struct {
|
|
REC(DIRMETA) m;
|
|
} d;
|
|
} u;
|
|
} *rc;
|
|
|
|
unsigned sz = sizeof rc->m + (type == FileEntry ? sizeof rc->u.f : sizeof rc->u.d);
|
|
rc = range_get(&uc2->cdir_range, sz);
|
|
if (!rc)
|
|
return UC2_Truncated;
|
|
|
|
diag("%X %08X [%.11s] ", type, get32(rc->m.parent), rc->m.name);
|
|
if (type == FileEntry) diag("(C:%-3u M:%-2u O:%-5X) %7d %7d\n",
|
|
get16(rc->u.f.c.method), get32(rc->u.f.c.masterPrefix), get32(rc->u.f.l.offset),
|
|
get32(rc->u.f.m.length), get32(rc->u.f.c.compressedLength));
|
|
else diag("%08X\n", get32(rc->u.d.m.index));
|
|
|
|
if (e) {
|
|
e->dirid = get32(rc->m.parent);
|
|
e->dos_time = get32(rc->m.time);
|
|
e->attr = rc->m.attrib;
|
|
if (type == FileEntry) {
|
|
e->id = 0;
|
|
e->size = get32(rc->u.f.m.length);
|
|
e->csize = get32(rc->u.f.c.compressedLength);
|
|
if (get32(rc->u.f.l.volume) != 1)
|
|
return UC2_Unimplemented;
|
|
e->xi.offset = get32(rc->u.f.l.offset);
|
|
e->xi.master = get32(rc->u.f.c.masterPrefix);
|
|
e->xi.csum = get16(rc->u.f.m.fletch);
|
|
e->xi.method = get16(rc->u.f.c.method);
|
|
e->is_dir = 0;
|
|
} else {
|
|
e->id = get32(rc->u.d.m.index);
|
|
e->size = e->csize = 0;
|
|
e->xi = (struct uc2_xinfo){0};
|
|
e->is_dir = 1;
|
|
}
|
|
e->has_tags = !!rc->m.tag;
|
|
copy_dos_name(e->dos_name, rc->m.name);
|
|
e->name_len = 0; // we'll fill the name later
|
|
if (!e->has_tags)
|
|
assemble_name(e);
|
|
}
|
|
return rc->m.tag ? UC2_TaggedEntry : UC2_BareEntry;
|
|
}
|
|
|
|
int uc2_get_tag(struct uc2_context *uc2, struct uc2_entry *e, char **tag, void **data, unsigned *len)
|
|
{
|
|
if (uc2->cdir_state != AtTag)
|
|
return UC2_UserFault;
|
|
|
|
for (;;) {
|
|
REC(EXTMETA) *x = range_get(&uc2->cdir_range, sizeof *x);
|
|
if (!x)
|
|
return cdir_damaged(uc2);
|
|
unsigned size = get32(x->size);
|
|
u8 *p = range_get(&uc2->cdir_range, size);
|
|
if (!p)
|
|
return cdir_damaged(uc2);
|
|
diag(" \"%.16s\" %u\n", x->tag, size);
|
|
|
|
if (e && memcmp(x->tag, TAG_LONGNAME, sizeof TAG_LONGNAME) == 0) {
|
|
u8 *z = memchr(p, 0, size);
|
|
if (!z) z = p + size;
|
|
copy_long_name(e, p, z);
|
|
}
|
|
|
|
uc2->cdir_state = x->next ? AtTag : AtEntry;
|
|
if (tag) {
|
|
*tag = (char*)x->tag;
|
|
if (data)
|
|
*data = p;
|
|
if (len)
|
|
*len = size;
|
|
}
|
|
if (!x->next)
|
|
break;
|
|
if (tag)
|
|
return x->next ? UC2_TaggedEntry : UC2_End;
|
|
}
|
|
if (e && e->name_len == 0)
|
|
assemble_name(e);
|
|
return UC2_End;
|
|
}
|
|
|
|
int uc2_finish_cdir(struct uc2_context *uc2, char label[12])
|
|
{
|
|
int ret;
|
|
|
|
if (uc2->cdir_state != AtTail) {
|
|
ret = uc2_read_cdir(uc2, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
assert(uc2->cdir_state == AtTail);
|
|
}
|
|
|
|
struct {
|
|
REC(XTAIL) xtail;
|
|
u32le aserial;
|
|
} *t = range_get(&uc2->cdir_range, sizeof *t);
|
|
if (!t)
|
|
return UC2_Truncated;
|
|
|
|
if (label) {
|
|
u8 *p = memchr(t->xtail.label, 0, 11);
|
|
if (!p) p = t->xtail.label + 11;
|
|
while (p > t->xtail.label && p[-1] == ' ') p--;
|
|
memcpy(label, t->xtail.label, p - t->xtail.label);
|
|
label[p - t->xtail.label] = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void copy_dos_name(u8 *dos_name, u8 *s)
|
|
{
|
|
u8 *d = dos_name;
|
|
u8 *z = d + 8;
|
|
for (;;) {
|
|
do {
|
|
u8 c = *s++;
|
|
if (!c) {
|
|
do {
|
|
*d++ = ' ';
|
|
} while (d < z);
|
|
break;
|
|
}
|
|
*d++ = c;
|
|
} while (d < z);
|
|
d = dos_name + 8;
|
|
if (d < z)
|
|
break;
|
|
z = d + 3;
|
|
}
|
|
}
|
|
|
|
static int cdir_damaged(struct uc2_context *uc2)
|
|
{
|
|
uc2->message = "Damaged central directory";
|
|
return UC2_Damaged;
|
|
}
|
|
|
|
/* delta */
|
|
|
|
struct delta {
|
|
u8 size;
|
|
u8 index;
|
|
u8 val[8];
|
|
};
|
|
|
|
static void delta_init(struct delta *db, u8 type)
|
|
{
|
|
struct delta d = {.size = type};
|
|
*db = d;
|
|
}
|
|
|
|
static void delta_apply(struct delta *db, u8 *p, unsigned size)
|
|
{
|
|
struct delta d = *db;
|
|
while (size--) {
|
|
u8 v = *p;
|
|
*p++ = v - d.val[d.index];
|
|
d.val[d.index] = v;
|
|
if (++d.index == d.size)
|
|
d.index = 0;
|
|
}
|
|
*db = d;
|
|
}
|
|
|
|
static void delta_revert(struct delta *db, u8 *dst, const u8 *src, unsigned size)
|
|
{
|
|
struct delta d = *db;
|
|
while (size--) {
|
|
u8 v = *src++ + d.val[d.index];
|
|
d.val[d.index] = *dst++ = v;
|
|
if (++d.index == d.size)
|
|
d.index = 0;
|
|
}
|
|
*db = d;
|
|
}
|
|
|
|
/* extract */
|
|
|
|
int uc2_extract(
|
|
struct uc2_context *uc2,
|
|
struct uc2_xinfo *xi,
|
|
unsigned size,
|
|
int (*write)(void *context, const void *ptr, unsigned len),
|
|
void *context)
|
|
{
|
|
int ret;
|
|
|
|
if (!uc2->scanned)
|
|
return UC2_BadState;
|
|
ret = resolve_master(uc2, xi->master);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
struct archive_ctx ar = {.offset=xi->offset, .uc2 = uc2};
|
|
struct reader rd = {.read = archive_read, .context = &ar};
|
|
struct user_write_ctx uw_ctx = {.write=write, .context=context};
|
|
struct writer wr = {.write = user_write, .context = &uw_ctx};
|
|
u16 csum;
|
|
ret = decompressor(uc2, xi->method, &rd, &wr, xi->master, size, &csum);
|
|
diag("decompressor ret:%d csum:%04X (expected:%04X)\n", ret, csum, xi->csum);
|
|
if (ret >= 0 && csum != xi->csum)
|
|
ret = UC2_Damaged;
|
|
return ret;
|
|
}
|
|
|
|
/* decompress */
|
|
|
|
static int decompressor_ultra(struct uc2_context *uc2, unsigned master, unsigned delta, struct reader *rd, struct writer *wr, unsigned limit, u16 *csum);
|
|
static int decompressor_rans(struct uc2_context *uc2, unsigned master_id, struct reader *rd, struct writer *wr, unsigned limit, u16 *csum);
|
|
|
|
static int decompressor(struct uc2_context *uc2, int method, struct reader *rd, struct writer *wr, unsigned master, unsigned len, u16 *csum)
|
|
{
|
|
unsigned delta;
|
|
int ret = UC2_Damaged;
|
|
|
|
diag("Decompressor method:%d master:%X\n", method, master);
|
|
if (method >= 1 && method <= 9) {
|
|
delta = 0;
|
|
ultra:
|
|
if (delta) diag("Using delta %d\n", delta);
|
|
ret = decompressor_ultra(uc2, master, delta, rd, wr, len, csum);
|
|
} else if (method >= 30 && method <= 39) {
|
|
delta = method - 29;
|
|
goto ultra;
|
|
} else if (method >= 40 && method <= 49) {
|
|
delta = method - 39;
|
|
goto ultra;
|
|
} else if (method >= 21 && method <= 29) {
|
|
delta = 1;
|
|
goto ultra;
|
|
} else if (method == 10) {
|
|
ret = decompressor_rans(uc2, master, rd, wr, len, csum);
|
|
} else if (method == 80) {
|
|
uc2->message = "Turbo compression not implemented";
|
|
ret = UC2_Unimplemented;
|
|
}
|
|
diag("Decompressor end\n");
|
|
return ret;
|
|
}
|
|
|
|
/* rANS decompressor (method 10) */
|
|
static int decompressor_rans(struct uc2_context *uc2, unsigned master_id,
|
|
struct reader *rd, struct writer *wr,
|
|
unsigned limit, u16 *csum)
|
|
{
|
|
const unsigned EOB = 64001;
|
|
int ret;
|
|
|
|
u8 *buf = u_alloc(uc2, 65536);
|
|
if (!buf) return UC2_UserFault;
|
|
|
|
ret = use_master(uc2, buf, master_id);
|
|
if (ret < 0) { u_free(uc2, buf); return ret; }
|
|
u16 tail = (u16)ret;
|
|
struct csum cs;
|
|
csum_init(&cs);
|
|
unsigned remaining = limit;
|
|
|
|
struct bits bi;
|
|
ret = bits_init(&bi, rd);
|
|
if (ret < 0) { u_free(uc2, buf); return ret; }
|
|
|
|
while (bits_get(&bi, 1)) { /* block-present */
|
|
unsigned nsyms = (bits_get(&bi, 8) << 8) | bits_get(&bi, 8);
|
|
unsigned rlen = (bits_get(&bi, 8) << 8) | bits_get(&bi, 8);
|
|
|
|
u32 freqs[344];
|
|
for (int i = 0; i < 344; i++)
|
|
freqs[i] = bits_get(&bi, 12);
|
|
|
|
struct uc2_rans_table tab;
|
|
uc2_rans_build_table(&tab, freqs, 344);
|
|
|
|
u8 *rdata = u_alloc(uc2, rlen ? rlen : 1);
|
|
if (!rdata) { bits_destroy(&bi); u_free(uc2, buf); return UC2_UserFault; }
|
|
for (unsigned i = 0; i < rlen; i++)
|
|
rdata[i] = (u8)bits_get(&bi, 8);
|
|
|
|
struct uc2_rans_dec dec;
|
|
uc2_rans_dec_init(&dec, &tab, rdata, rlen);
|
|
|
|
for (unsigned s = 0; s < nsyms && remaining > 0; s++) {
|
|
int sym = uc2_rans_decode(&dec);
|
|
if (sym < 256) {
|
|
buf[tail++] = (u8)sym;
|
|
remaining--;
|
|
} else if (sym < 316) {
|
|
int ds = sym - 256;
|
|
unsigned dist = (ds < 15) ? ds + 1 :
|
|
(ds < 30) ? (ds-15+1)*16 + bits_get(&bi, 4) :
|
|
(ds < 45) ? (ds-30+1)*256 + bits_get(&bi, 8) :
|
|
(ds-45+1)*4096 + bits_get(&bi, 12);
|
|
if (dist == EOB) { s++; if (s < nsyms) uc2_rans_decode(&dec); break; }
|
|
s++;
|
|
if (s >= nsyms) break;
|
|
int ls = uc2_rans_decode(&dec) - 316;
|
|
if (ls < 0) ls = 0;
|
|
unsigned length = (ls < 8) ? ls + 3 :
|
|
(ls < 16) ? (ls-8)*2+11+bits_get(&bi,1) :
|
|
(ls < 24) ? (ls-16)*8+27+bits_get(&bi,3) :
|
|
(ls == 24) ? 91+bits_get(&bi,6) :
|
|
(ls == 25) ? 155+bits_get(&bi,9) :
|
|
(ls == 26) ? 667+bits_get(&bi,11) :
|
|
2715+bits_get(&bi,15);
|
|
for (unsigned j = 0; j < length && remaining > 0; j++) {
|
|
buf[tail] = buf[(u16)(tail - dist)];
|
|
tail++; remaining--;
|
|
}
|
|
}
|
|
}
|
|
u_free(uc2, rdata);
|
|
}
|
|
|
|
/* Flush output */
|
|
u16 head = (u16)(tail - (limit - remaining));
|
|
if (limit > remaining) {
|
|
unsigned n = limit - remaining;
|
|
csum_update(&cs, buf + head, n);
|
|
ret = wr->write(wr->context, buf + head, n);
|
|
if (ret < 0) { bits_destroy(&bi); u_free(uc2, buf); return UC2_UserFault; }
|
|
}
|
|
|
|
bits_destroy(&bi);
|
|
u_free(uc2, buf);
|
|
if (csum) *csum = csum_get(&cs);
|
|
return limit - remaining;
|
|
}
|
|
|
|
/* cbuf */
|
|
|
|
struct cbuffer {
|
|
u16 head, tail;
|
|
unsigned limit;
|
|
struct csum csum;
|
|
u8 data[0x10000];
|
|
};
|
|
|
|
static unsigned cbuf_have(struct cbuffer *cb)
|
|
{
|
|
return (u16)(cb->tail - cb->head);
|
|
}
|
|
|
|
static unsigned cbuf_space(struct cbuffer *cb)
|
|
{
|
|
return 0x10000 - cbuf_have(cb) - 1;
|
|
}
|
|
|
|
static int cbuf_flush(struct writer *wr, struct cbuffer *cb, struct delta *db, u8 *dbuf)
|
|
{
|
|
for (;;) {
|
|
unsigned n = cbuf_have(cb);
|
|
if (!n) return 0;
|
|
unsigned u = 0x10000 - cb->head;
|
|
if (n > u) n = u;
|
|
if (cb->limit < n) {
|
|
diag("cbuf_flush %u < %u\a\n", cb->limit, n);
|
|
n = cb->limit;
|
|
}
|
|
u8 *p = cb->data + cb->head;
|
|
csum_update(&cb->csum, p, n);
|
|
if (dbuf) {
|
|
delta_revert(db, dbuf, p, n);
|
|
p = dbuf;
|
|
}
|
|
int r = wr->write(wr->context, p, n);
|
|
if (r < 0)
|
|
return r;
|
|
cb->head += n;
|
|
cb->limit -= n;
|
|
if (!cb->limit)
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* huffman */
|
|
|
|
enum {
|
|
MaxCodeBits = 13,
|
|
LookupSize = 1 << MaxCodeBits
|
|
};
|
|
|
|
static int huff(u32 table[LookupSize], struct bits *bi)
|
|
{
|
|
int b = bits_peek(bi, 13);
|
|
if (b < 0)
|
|
return b;
|
|
u32 c = table[b];
|
|
bits_skip(bi, c >> 24);
|
|
return c & 0xffffff;
|
|
}
|
|
|
|
enum {
|
|
NumByteSym = 256,
|
|
NumDistSym = 60,
|
|
NumLenSym = 28,
|
|
NumSymbols = NumByteSym + NumDistSym + NumLenSym,
|
|
|
|
NumLoAsciiSym = 28,
|
|
NumHiByteSym = 128
|
|
};
|
|
|
|
struct dcinfo {
|
|
u8 symprev[NumSymbols];
|
|
};
|
|
|
|
static void dc_init(struct dcinfo *dc);
|
|
static int ht_dec(u8 lengths[NumSymbols], struct dcinfo *dc, struct bits *bi, u32 table[LookupSize]);
|
|
static int ht_mktree(u32 table[LookupSize], const u8 *lengths, int nlit, int ncodes, const u32 *codes);
|
|
|
|
enum {
|
|
NumDeltaCodes = MaxCodeBits + 1,
|
|
NumExtraCodes = 1,
|
|
NumLenCodes = NumDeltaCodes + NumExtraCodes,
|
|
};
|
|
|
|
static const u8 vval[NumDeltaCodes][NumDeltaCodes] = {
|
|
{ 0,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
|
{ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0},
|
|
{ 2, 1, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0},
|
|
{ 3, 2, 4, 1, 5, 6, 7, 8, 9,10,11,12,13, 0},
|
|
{ 4, 3, 5, 2, 6, 1, 7, 8, 9,10,11,12,13, 0},
|
|
{ 5, 4, 6, 3, 7, 2, 8, 1, 9,10,11,12,13, 0},
|
|
{ 6, 5, 7, 4, 8, 3, 9, 2,10, 1,11,12,13, 0},
|
|
{ 7, 6, 8, 5, 9, 4,10, 3,11, 2,12, 1,13, 0},
|
|
{ 8, 7, 9, 6,10, 5,11, 4,12, 3,13, 2, 0, 1},
|
|
{ 9, 8,10, 7,11, 6,12, 5,13, 4, 0, 3, 2, 1},
|
|
{10, 9,11, 8,12, 7,13, 6, 0, 5, 4, 3, 2, 1},
|
|
{11,10,12, 9,13, 8, 0, 7, 6, 5, 4, 3, 2, 1},
|
|
{12,11,13,10, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
|
{13,12, 0,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
|
};
|
|
|
|
static void default_lengths(u8 d[NumSymbols])
|
|
{
|
|
static const u8 rle[] = {
|
|
10,9, 1,7, 1,9, 1,7, 19,9, 1,7, 13,8, 1,7, 11,8, 1,7, 33,8, 1,7, 35,8, 128,10, 16,6, 12,7, 6,8, 10,9, 16,10, 9,4, 9,5, 10,6, 0
|
|
};
|
|
const u8 *s = rle;
|
|
u8 n = s[0];
|
|
do {
|
|
u8 v = s[1];
|
|
s += 2;
|
|
do {
|
|
*d++ = v;
|
|
} while (--n);
|
|
n = *s;
|
|
} while (n);
|
|
}
|
|
|
|
static void dc_init(struct dcinfo *dc)
|
|
{
|
|
default_lengths(dc->symprev);
|
|
}
|
|
|
|
enum {
|
|
RepeatCode = MaxCodeBits + 1,
|
|
MinRepeat = 6
|
|
};
|
|
|
|
static int ht_dec(u8 lengths[NumSymbols], struct dcinfo *dc, struct bits *bi, u32 table[LookupSize])
|
|
{
|
|
int t = bits_get(bi, 1);
|
|
if (t <= 0) {
|
|
if (t == 0) {
|
|
default_lengths(dc->symprev);
|
|
default_lengths(lengths);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
diag("New tree\n");
|
|
t = bits_get(bi, 2);
|
|
if (t < 0)
|
|
return t;
|
|
|
|
u8 tlengths[NumLenCodes];
|
|
for (int i = 0; i < NumLenCodes; i++) {
|
|
int b = bits_get(bi, 3);
|
|
if (b < 0)
|
|
return b;
|
|
tlengths[i] = (u8)b;
|
|
}
|
|
|
|
ht_mktree(table, tlengths, NumLenCodes, 0, 0);
|
|
|
|
u8 stream[NumSymbols];
|
|
u8 *symp = stream;
|
|
u8 *syme = stream + NumSymbols - NumLoAsciiSym - NumHiByteSym;
|
|
if (t & 1)
|
|
syme += NumLoAsciiSym;
|
|
if (t & 2)
|
|
syme += NumHiByteSym;
|
|
|
|
u8 val = 0;
|
|
do {
|
|
int c = huff(table, bi);
|
|
if (c < 0)
|
|
return c;
|
|
if (c == RepeatCode) {
|
|
c = huff(table, bi);
|
|
if (c < 0)
|
|
return c;
|
|
int n = c + MinRepeat - 1;
|
|
for (; n > 0; n--)
|
|
*symp++ = val;
|
|
} else {
|
|
val = c;
|
|
*symp++ = c;
|
|
}
|
|
} while (symp < syme);
|
|
|
|
static const u16 rle[][8] = {
|
|
{0x009, 0x202, 0x1, 0x202, 0x12, 0x260, 0x80, 0x258},
|
|
{0x280, 0x80, 0x258},
|
|
{0x009, 0x202, 0x1, 0x202, 0x12, 0x338},
|
|
{0x358}
|
|
};
|
|
const u16 *p = rle[t];
|
|
int i = 0;
|
|
symp = stream;
|
|
do {
|
|
u16 v = *p++;
|
|
int e = i + (v & 0x1ff);
|
|
do {
|
|
lengths[i] = v & 0x200 ? vval[dc->symprev[i]][*symp++] : 0;
|
|
} while (++i < e);
|
|
} while (symp < syme);
|
|
|
|
for (int i = 0; i < NumSymbols; i++) {
|
|
assert(lengths[i] <= 13);
|
|
dc->symprev[i] = lengths[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ht_mktree(u32 table[LookupSize], const u8 *lengths, int nlit, int ncodes, const u32 *codes)
|
|
{
|
|
int nsym = nlit + ncodes;
|
|
u32 *p = table;
|
|
u32 *e = table + LookupSize;
|
|
|
|
for (int l = 1; l <= MaxCodeBits; l++) {
|
|
for (int i = 0; i < nsym; i++) {
|
|
if (lengths[i] == l) {
|
|
int n = 1 << (MaxCodeBits - l);
|
|
if (p + n > e)
|
|
return UC2_Damaged;
|
|
u32 c = i < nlit ? i : codes[i - nlit];
|
|
c |= l << 24;
|
|
do {
|
|
*p++ = c;
|
|
} while (--n);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if (p != e)
|
|
// return UC2_Damaged;
|
|
while (p < e)
|
|
*p++ = 1<<24;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ultra */
|
|
|
|
struct ultra {
|
|
struct bits bi;
|
|
struct dcinfo dc;
|
|
struct cbuffer cb;
|
|
|
|
u32 bd_table[LookupSize];
|
|
u32 l_table[LookupSize];
|
|
};
|
|
|
|
static int decode_ht(struct ultra *ultra);
|
|
static int decompress_block(struct ultra *ultra);
|
|
|
|
enum {
|
|
End,
|
|
More
|
|
};
|
|
|
|
static int decompressor_ultra(struct uc2_context *uc2, unsigned master, unsigned delta, struct reader *rd, struct writer *wr, unsigned limit, u16 *csum)
|
|
{
|
|
diag("decompressor_ultra master:%X limit:%u\n", master, limit);
|
|
|
|
int ret;
|
|
|
|
struct ultra *ultra = u_alloc(uc2, sizeof *ultra);
|
|
if (!ultra)
|
|
return UC2_UserFault;
|
|
|
|
ret = use_master(uc2, ultra->cb.data, master);
|
|
if (ret < 0)
|
|
goto ret;
|
|
ultra->cb.limit = limit;
|
|
ultra->cb.head = ultra->cb.tail = ret;
|
|
csum_init(&ultra->cb.csum);
|
|
|
|
u8 *dbuf = 0;
|
|
struct delta db;
|
|
if (delta) {
|
|
if (master != SuperMaster) {
|
|
delta_init(&db, delta);
|
|
delta_apply(&db, ultra->cb.data, ultra->cb.tail);
|
|
}
|
|
dbuf = u_alloc(uc2, sizeof ultra->cb.data);
|
|
ret = UC2_UserFault;
|
|
if (!dbuf)
|
|
goto ret;
|
|
delta_init(&db, delta);
|
|
}
|
|
|
|
ret = bits_init(&ultra->bi, rd);
|
|
if (ret < 0)
|
|
goto ret2;
|
|
|
|
dc_init(&ultra->dc);
|
|
for (;;) {
|
|
ret = decode_ht(ultra);
|
|
if (ret <= 0)
|
|
break;
|
|
for (;;) {
|
|
int o = decompress_block(ultra);
|
|
ret = cbuf_flush(wr, &ultra->cb, &db, dbuf);
|
|
if (ret < 0)
|
|
goto ret2;
|
|
if (o != More)
|
|
break;
|
|
}
|
|
}
|
|
bits_destroy(&ultra->bi);
|
|
if (csum)
|
|
*csum = csum_get(&ultra->cb.csum);
|
|
ret = limit - ultra->cb.limit;
|
|
ret2:
|
|
u_free(uc2, dbuf);
|
|
ret:
|
|
u_free(uc2, ultra);
|
|
return ret;
|
|
}
|
|
|
|
static int decode_ht(struct ultra *ultra)
|
|
{
|
|
int ret = bits_get(&ultra->bi, 1);
|
|
if (ret > 0) {
|
|
u8 lengths[NumSymbols];
|
|
u32 *tmp = ultra->bd_table;
|
|
ret = ht_dec(lengths, &ultra->dc, &ultra->bi, tmp);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
#define D(V,B) ((B)<<20|1<<16|(V))
|
|
static const u32 d_codes[NumDistSym] = {
|
|
D(1,0), D(2,0), D(3,0), D(4,0), D(5,0), D(6,0), D(7,0), D(8,0),
|
|
D(9,0), D(10,0), D(11,0), D(12,0), D(13,0), D(14,0), D(15,0), D(16,4),
|
|
D(32,4), D(48,4), D(64,4), D(80,4), D(96,4), D(112,4), D(128,4), D(144,4),
|
|
D(160,4), D(176,4), D(192,4), D(208,4), D(224,4), D(240,4), D(256,8), D(512,8),
|
|
D(768,8), D(1024,8), D(1280,8), D(1536,8), D(1792,8), D(2048,8), D(2304,8), D(2560,8),
|
|
D(2816,8), D(3072,8), D(3328,8), D(3584,8), D(3840,8), D(4096,12), D(8192,12), D(12288,12),
|
|
D(16384,12),D(20480,12),D(24576,12),D(28672,12),D(32768,12),D(36864,12),D(40960,12),D(45056,12),
|
|
D(49152,12),D(53248,12),D(57344,12),D(61440,12)
|
|
};
|
|
#undef D
|
|
ret = ht_mktree(ultra->bd_table, lengths, NumByteSym, NumDistSym, d_codes);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
#define L(V,B) ((B)<<20|(V))
|
|
static const u32 l_codes[NumLenSym] = {
|
|
L(3,0), L(4,0), L(5,0), L(6,0), L(7,0), L(8,0), L(9,0), L(10,0),
|
|
L(11,1), L(13,1), L(15,1), L(17,1), L(19,1), L(21,1), L(23,1), L(25,1),
|
|
L(27,3), L(35,3), L(43,3), L(51,3), L(59,3), L(67,3), L(75,3), L(83,3),
|
|
L(91,6), L(155,9), L(667,11), L(2715,15)
|
|
};
|
|
#undef L
|
|
ret = ht_mktree(ultra->l_table, lengths + NumByteSym + NumDistSym, 0, NumLenSym, l_codes);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int decompress_block(struct ultra *ultra)
|
|
{
|
|
const unsigned EOB_MARK = 125*512+1;
|
|
|
|
do {
|
|
int c = huff(ultra->bd_table, &ultra->bi);
|
|
if (c < 0)
|
|
return c;
|
|
if (!(c & 1<<16))
|
|
ultra->cb.data[ultra->cb.tail++] = (u8)c;
|
|
else {
|
|
unsigned dist = c & 0xffff;
|
|
c = c >> 20 & 0xf;
|
|
if (c)
|
|
dist += bits_get(&ultra->bi, c);
|
|
|
|
c = huff(ultra->l_table, &ultra->bi);
|
|
if (c < 0)
|
|
return c;
|
|
|
|
if (dist == EOB_MARK)
|
|
return End;
|
|
|
|
unsigned len = c & 0xffff;
|
|
c = c >> 20 & 0xf;
|
|
if (c)
|
|
len += bits_get(&ultra->bi, c);
|
|
assert(cbuf_space(&ultra->cb) >= len);
|
|
do {
|
|
ultra->cb.data[ultra->cb.tail] = ultra->cb.data[(u16)(ultra->cb.tail - dist)];
|
|
ultra->cb.tail++;
|
|
} while (--len);
|
|
}
|
|
|
|
} while (cbuf_space(&ultra->cb) >= 35482);
|
|
|
|
return More;
|
|
}
|
|
|
|
/* initial */
|
|
|
|
static int start_read(struct uc2_context *uc2)
|
|
{
|
|
int ret = 0;
|
|
struct {
|
|
REC(FHEAD) fhead;
|
|
REC(XHEAD) xhead;
|
|
} h;
|
|
ret = u_read_all(uc2, 0, &h, sizeof h);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!uc2_identify(&h, sizeof h)) {
|
|
not_uc2:
|
|
uc2->message = "Not an UC2 archive";
|
|
return UC2_Damaged;
|
|
}
|
|
|
|
int ver = get16(h.xhead.versionNeededToExtract);
|
|
|
|
diag("Cdir offset:%u made:%d need:%d\n",
|
|
get32(h.xhead.cdir.offset), get16(h.xhead.versionMadeBy), ver);
|
|
|
|
|
|
if (ver >= 203) {
|
|
if (ver > 203)
|
|
goto not_uc2;
|
|
uc2->pcp = 1;
|
|
}
|
|
|
|
return decompress_cdir(uc2, get32(h.xhead.cdir.offset), get16(h.xhead.fletch));
|
|
}
|
|
|
|
/* public */
|
|
|
|
struct uc2_context *uc2_open(struct uc2_io *io, void *io_ctx)
|
|
{
|
|
struct uc2_context *uc2 = io->alloc(io_ctx, sizeof *uc2);
|
|
if (uc2) {
|
|
uc2->message = 0;
|
|
uc2->io = io;
|
|
uc2->io_ctx = io_ctx;
|
|
uc2->supermaster = 0;
|
|
uc2->cdir_buf = 0;
|
|
uc2->cdir_state = Start;
|
|
uc2->scanned = 0;
|
|
uc2->pcp = 0;
|
|
list_init(&uc2->masters);
|
|
}
|
|
return uc2;
|
|
}
|
|
|
|
struct uc2_context *uc2_close(struct uc2_context *uc2)
|
|
{
|
|
if (uc2) {
|
|
struct list *l = uc2->masters.next;
|
|
while (l != &uc2->masters) {
|
|
struct master_info *mi = list_item(l, struct master_info, list);
|
|
l = l->next;
|
|
u_free(uc2, mi->data);
|
|
u_free(uc2, mi);
|
|
}
|
|
u_free(uc2, uc2->supermaster);
|
|
u_free(uc2, uc2->cdir_buf);
|
|
uc2 = u_free(uc2, uc2);
|
|
}
|
|
return uc2;
|
|
}
|
|
|
|
const char *uc2_message(struct uc2_context *uc2, int ret)
|
|
{
|
|
const char *s = uc2->message;
|
|
uc2->message = 0;
|
|
if (!s) {
|
|
static const char *tab[] = {
|
|
[~UC2_UserFault] = "Callback fault",
|
|
[~UC2_BadState] = "Bad state",
|
|
[~UC2_Damaged] = "Damaged archive",
|
|
[~UC2_Truncated] = "Truncated",
|
|
[~UC2_Unimplemented] = "Unimplemented",
|
|
[~UC2_InternalError] = "Internal error"
|
|
};
|
|
if (~ret >= 0) {
|
|
if (~ret < elemof(tab))
|
|
s = tab[~ret];
|
|
if (!s)
|
|
s = "Error";
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/* Decompress the built-in SuperMaster (49152 bytes) into caller's buffer.
|
|
Returns 49152 on success, negative UC2_* error code on failure. */
|
|
static void *sm_alloc(void *ctx, unsigned size) { (void)ctx; return malloc(size); }
|
|
static void sm_free(void *ctx, void *ptr) { (void)ctx; free(ptr); }
|
|
|
|
int uc2_get_supermaster(void *buf, unsigned buf_size)
|
|
{
|
|
if (buf_size < 49152)
|
|
return UC2_UserFault;
|
|
|
|
struct uc2_io io = { .alloc = sm_alloc, .free = sm_free };
|
|
struct uc2_context *uc2 = uc2_open(&io, NULL);
|
|
if (!uc2)
|
|
return UC2_UserFault;
|
|
|
|
int ret = resolve_master(uc2, SuperMaster);
|
|
if (ret >= 0)
|
|
memcpy(buf, uc2->supermaster, 49152);
|
|
|
|
uc2_close(uc2);
|
|
return ret < 0 ? ret : 49152;
|
|
}
|