Can embed any file in a PNG/WebM/GIF/JPEG and upload it to a third-party host through 4chan
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

631 lines
19 KiB

import { BitstreamReader, BitstreamWriter, revbyte } from './bitstream';
const TINF_OK = 0;
const TINF_DATA_ERROR = -3;
class Tree {
table = new Uint16Array(16); /* table of code length counts */
trans = new Uint16Array(288); /* code -> symbol translation table */
}
type HCtree = [number | HCtree, (number | HCtree)?];
// these two functions are a big bottleneck because im not clever enough to figure out how to encode
// something directly by using the sorted code length/value tables, haha
const getPathTo = (tree: HCtree, value: number): string | undefined => {
if (tree[0] === value)
return '0';
if (tree[1] === value)
return '1';
let p: string | undefined;
if (typeof tree[0] != "number")
p = getPathTo(tree[0], value);
let b = '0';
if (!p) {
if (tree[1] && typeof tree[1] != "number")
p = getPathTo(tree[1], value);
b = '1';
}
if (p)
return b + p;
};
// from jpeg-js, in turns this means that jpeg-js decoding could be faster
// if they decoded directly from the symbol tables instead of building a tree
function buildHuffmanTable(codeLengths: ArrayLike<number>, values: ArrayLike<number>) {
// eslint-disable-next-line prefer-const
let k = 0, code: any = [], i, j, length = 16;
while (length > 0 && !codeLengths[length - 1])
length--;
code.push({ children: [], index: 0 });
let p = code[0], q;
for (i = 0; i < length; i++) {
for (j = 0; j < codeLengths[i]; j++) {
p = code.pop();
p.children[p.index] = values[k];
while (p.index > 0) {
if (code.length === 0)
throw new Error('Could not recreate Huffman Table');
p = code.pop();
}
p.index++;
code.push(p);
while (code.length <= i) {
code.push(q = { children: [], index: 0 });
p.children[p.index] = q.children;
p = q;
}
k++;
}
if (i + 1 < length) {
// p here points to last code
code.push(q = { children: [], index: 0 });
p.children[p.index] = q.children;
p = q;
}
}
return code[0].children as HCtree;
}
class Data {
pathMap = new Map<HCtree, Map<number, {
length: number,
val: number
}>>();
computeReverse() {
this.rltree = buildHuffmanTable(this.ltree.table, this.ltree.trans)[0]! as any; // unneeded, but maybe sometime i'll throw symbol reduction into the mix
this.rdtree = buildHuffmanTable(this.dtree.table, this.dtree.trans)[0]! as any;
this.adists = new Set(this.rdtree.flat(16) as number[]);
}
ltree: Tree;
dtree: Tree;
rltree!: HCtree;
rdtree!: HCtree;
adists!: Set<number>;
dest: number[] = [];
constructor(public source: BitstreamReader, public dests: BitstreamWriter | null, public to_hide?: BitstreamReader | BitstreamWriter, public hidden?: BitstreamWriter) {
this.ltree = new Tree(); /* dynamic length/symbol tree */
this.dtree = new Tree(); /* dynamic distance tree */
}
}
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
const sltree = new Tree();
const sdtree = new Tree();
// eslint-disable-next-line prefer-const
let rltree: HCtree;
// eslint-disable-next-line prefer-const
let rdtree: HCtree;
// eslint-disable-next-line prefer-const
let sadist: Set<number>;
/* extra bits and base tables for length codes */
const length_bits = new Uint8Array(30);
const length_base = new Uint16Array(30);
/* extra bits and base tables for distance codes */
const dist_bits = new Uint8Array(30);
const dist_base = new Uint16Array(30);
/* special ordering of code length codes */
const clcidx = new Uint8Array([
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
]);
/* used by tinf_decode_trees, avoids allocations every call */
const code_tree = new Tree();
const lengths = new Uint8Array(288 + 32);
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
function tinf_build_bits_base(bits: Uint8Array, base: Uint16Array, delta: number, first: number) {
let i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0;
/* build base table */
for (sum = first, i = 0; i < 30; ++i) {
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
function tinf_build_fixed_trees(lt: Tree, dt: Tree) {
let i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt.table[i] = 0;
lt.table[7] = 24;
lt.table[8] = 152;
lt.table[9] = 112;
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt.table[i] = 0;
dt.table[5] = 32;
for (i = 0; i < 32; ++i) dt.trans[i] = i;
}
/* given an array of code lengths, build a tree */
const offs = new Uint16Array(16);
function tinf_build_tree(t: Tree, lengths: Uint8Array, off: number, num: number) {
let i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t.table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t.table[lengths[off + i]]++;
t.table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t.table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i) {
if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* get one bit from source stream */
function tinf_getbit(d: Data) {
const v = d.source.readSync(1);
return v;
}
let loff = 0;
const loffs: number[] = [];
/* read a num bit value from a stream and add base */
function tinf_read_bits(d: Data, num: number, base: number) {
if (!num)
return base;
const v = d.source.readSync(num) + base;
loff = v;
//console.log(v);
loffs.push(v);
if (loffs.length > 4) {
loffs.shift();
}
return v;
}
/* given a data stream and a tree, decode a symbol */
function tinf_decode_symbol(d: Data, t: Tree, copy = true, ext: any = {}) {
let sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
let s = 0;
do {
const b = d.source.readSync(1);
copy && d.hidden?.write(1, b);
s = (s << 1) | b;
cur = 2 * cur + b;
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
ext.length = len;
ext.sym = s;
return t.trans[sum + cur];
}
function tinf_decode_symbol2(d: Data, t: Tree) {
let sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
do {
const b = d.source.readSync(1);
//d.hidden.write(1, b);
cur = 2 * cur + b;
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
return t.trans[sum + cur];
}
/* given a data stream, decode dynamic trees from it */
function tinf_decode_trees(d: Data, lt: Tree, dt: Tree, copy = true) {
let i, num, length;
/* get 5 bits HLIT (257-286) */
const hlit = tinf_read_bits(d, 5, 257);
copy && d.hidden?.write(5, hlit - 257);
/* get 5 bits HDIST (1-32) */
const hdist = tinf_read_bits(d, 5, 1);
copy && d.hidden?.write(5, hdist - 1);
/* get 4 bits HCLEN (4-19) */
const hclen = tinf_read_bits(d, 4, 4);
copy && d.hidden?.write(4, hclen - 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i) {
/* get 3 bits code length (0-7) */
const clen = tinf_read_bits(d, 3, 0);
copy && d.hidden?.write(3, clen);
lengths[clcidx[i]] = clen;
}
/* build code length tree */
tinf_build_tree(code_tree, lengths, 0, 19);
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist;) {
const sym = tinf_decode_symbol(d, code_tree, copy);
let prev: number;
switch (sym) {
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
prev = lengths[num - 1];
length = tinf_read_bits(d, 2, 3);
copy && d.hidden?.write(2, length - 3);
for (; length; --length) {
lengths[num++] = prev;
}
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
length = tinf_read_bits(d, 3, 3);
copy && d.hidden?.write(3, length - 3);
for (; length; --length) {
lengths[num++] = 0;
}
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
length = tinf_read_bits(d, 7, 11);
copy && d.hidden?.write(7, length - 11);
for (; length; --length) {
lengths[num++] = 0;
}
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
break;
}
}
/* build dynamic trees */
tinf_build_tree(lt, lengths, 0, hlit);
tinf_build_tree(dt, lengths, hlit, hdist);
}
const get_symbol = (value: number, bits_table: Uint8Array, base_table: Uint16Array): [number, number, number] => {
let i = 0;
for (i = 0; i < base_table.length; ++i) {
if (base_table[i] > value) {
i--;
return [i, bits_table[i], value - base_table[i]];
}
}
i--;
return [i, bits_table[i], value - base_table[i]];
};
const encode_symbol = (sym: number, tree: HCtree, pathMap: Map<HCtree, Map<number, { length: number, val: number }>>) => {
/*let m: Map<number, { length: number, val: number }> | undefined;
if ((m = pathMap.get(tree))) {
const v = m.get(sym);
if (v) return v;
} else {
m = new Map;
pathMap.set(tree, m);
}*/
const code = getPathTo(tree, sym)!;
const v = {
length: code?.length,
val: parseInt(code, 2)
};
//m.set(sym, v);
return v;
};
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
export let capacity = 0;
function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
// eslint-disable-next-line no-constant-condition
while (1) {
let sym = tinf_decode_symbol(d, lt); // copy
/* check for end of block */
if (sym === 256) {
return TINF_OK;
}
if (sym < 256) {
d.dest.push(sym);
// same
} else {
sym -= 257;
/* possibly get more bits from length code */
const length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
//d.hidden.write(length_bits[sym], length - length_base[sym]);
// length is unchanged, so copy as is
if (length_bits[sym])
d.hidden?.write(length_bits[sym], length - length_base[sym]);
const ext = { length: 0, sym: 0 };
const dist = tinf_decode_symbol(d, dt, false, ext); // don't copy immediately, we may change the code
//ext.sym = revbyte(ext.sym, ext.length);
//d.hidden.write(ext.length, ext.sym);
/* possibly get more bits from distance code */
let backoffset = tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
const offs = d.dest.length - backoffset;
let match: Buffer;
// don't consider matches that could be in the lookahead buffer
const skip = !d.to_hide || (d.to_hide && d.to_hide instanceof BitstreamReader && d.to_hide.available == 0);
if (!skip && (match = Buffer.from(d.dest.slice(offs, offs + length))).length == length) {
let begin = d.dest.length - 32768;
if (begin < 0)
begin = 0;
let matches: number[] = [];
let o = 0;
const slic = Buffer.from(d.dest.slice(begin + o, d.dest.length));
while (begin + o < d.dest.length) {
const r = slic.slice(o, d.dest.length).indexOf(match);
if (r >= 0) {
matches.push(r + begin + o);
o += r;
}
else {
break;
}
o++;
}
if (matches.length > 1) {
matches = matches.map(e => -(e - d.dest.length)).filter(e => {
const [dsym] = get_symbol(e, dist_bits, dist_base);
return d.adists.has(dsym);
});
matches.reverse();
const v = Math.floor(Math.log2(matches.length));
capacity += v;
// a ""perfectly"" compressed file is like a file with only 0s embedded
//console.log('LLLL', matches.length)
if (d.to_hide instanceof BitstreamReader) {
if (d.to_hide.available) {
const s = d.to_hide.readSync(Math.min(d.to_hide.available, v));
backoffset = matches[s];
}
}
// extract hidden bit
else {
const idx = matches.indexOf(backoffset);
d.to_hide!.write(v, idx);
}
}
}
// match length should be the same so no need to rewrite
//const [lsym, llen, loff] = get_symbol(length, length_bits, length_base)
//let enclen = encode_symbol(lsym, d.rltree);
//d.hidden.write(enclen.length, enclen.val);
//d.hidden.write(llen, loff);
const [dsym, dlen, doff] = get_symbol(backoffset, dist_bits, dist_base);
const encdist = encode_symbol(dsym, d.rdtree, d.pathMap);
d.hidden?.write(encdist.length, revbyte(encdist.val, encdist.length));
d.hidden?.write(dlen, doff);
/* copy match */
for (let i = offs; i < offs + length; ++i) {
d.dest.push(d.dest[i]);
}
}
}
}
/* inflate an uncompressed block of data */
function tinf_inflate_uncompressed_block(d: Data) {
if (d.source.offset & 7)
d.source.readSync(8 - d.source.offset & 7);
if (d.hidden && d.hidden.offset & 7)
d.hidden?.write(8 - d.hidden.offset & 7, 0);
const length = d.source.readSync(16);
d.hidden?.write(16, length);
/* get one's complement of length */
const invlength = d.source.readSync(16);
d.hidden?.write(16, invlength);
/* check length */
if (length !== (~invlength & 0x0000ffff)) {
//console.log(length, invlength, loff)
return -4;
}
for (let i = length; i; --i) {
const v = d.source.readSync(8);
d.dest.push(v);
d.hidden?.write(8, v);
}
return TINF_OK;
}
function copy_block_data(d: Data, lt: Tree, dt: Tree) {
// eslint-disable-next-line no-constant-condition
while (1) {
let sym = tinf_decode_symbol(d, lt, false); // copy
/* check for end of block */
if (sym === 256) {
break;
}
if (sym < 256) {
//d.dest.push(sym);
// same
} else {
sym -= 257;
const length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
const dist = tinf_decode_symbol2(d, dt);
tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
}
}
}
function copy_uncompressed_block(d: Data) {
// align
if (d.source.offset & 7)
d.source.readSync(8 - d.source.offset & 7);
if (d.hidden && d.hidden.offset & 7)
d.hidden.write(8 - d.hidden.offset & 7, 0);
const length: number = d.source.readSync(16);
d.hidden?.write(16, length);
d.hidden?.write(16, d.source.readSync(16));
let total = length * 8;
while (total) {
const r = Math.min(32, total);
d.hidden?.write(r, d.source.readSync(r));
total -= r;
}
return TINF_OK;
}
/* inflate stream from source to dest */
export function tinf_uncompress(source: BitstreamReader,
// normal decompressed data
decompressed?: (chunk: Uint8Array) => void,
// stream of data to hide
to_hide?: BitstreamReader | BitstreamWriter,
// compressed stream containing hidden data
hidden?: (chunk: Uint8Array) => void, opt = 0) {
const decomp = decompressed ? new BitstreamWriter({ write: decompressed }) : null;
const hid = hidden && new BitstreamWriter({ write: hidden }, 4);
const d = new Data(source, decomp, to_hide, hid);
let res: number | undefined | true;
let bfinal: number, btype: number;
do {
// TODO: truncate to_hide if we get close to 70k?
/* read final block flag */
bfinal = tinf_getbit(d);
d.hidden?.write(1, bfinal);
/* read block type (2 bits) */
btype = tinf_read_bits(d, 2, 0);
d.hidden?.write(2, btype);
/* decompress block */
//console.log(btype, capacity);
switch (btype) {
case 0:
/* decompress uncompressed block */
res = tinf_inflate_uncompressed_block(d);
break;
case 1:
/* decompress block with fixed huffman trees */
d.rdtree = rdtree;
d.rltree = rltree;
d.adists = sadist;
res = tinf_inflate_block_data(d, sltree, sdtree);
break;
case 2:
/* decompress block with dynamic huffman trees */
tinf_decode_trees(d, d.ltree, d.dtree);
d.computeReverse();
res = tinf_inflate_block_data(d, d.ltree, d.dtree);
break;
default:
res = -2;
}
if (res !== TINF_OK)
throw new Error('Data error ' + res);
} while (!bfinal);
decomp?.end();
hid?.end();
//if (d.dest.byteOffset < d.dest.length) {
// if (typeof d.dest.slice === 'function')
// return d.dest.slice(0, d.dest.byteOffset);
// else
// return d.dest.subarray(0, d.dest.byteOffset);
//}
//return d.dest;
}
/* -------------------- *
* -- initialization -- *
* -------------------- */
/* build fixed huffman trees */
tinf_build_fixed_trees(sltree, sdtree);
/* build extra bits and base tables */
tinf_build_bits_base(length_bits, length_base, 4, 3);
tinf_build_bits_base(dist_bits, dist_base, 2, 1);
rltree = buildHuffmanTable(sltree.table, sltree.trans)[0] as any;
rdtree = buildHuffmanTable(sdtree.table, sdtree.trans)[0] as any;
sadist = new Set(rdtree.flat(16) as number[]);
/* fix a special case */
length_bits[28] = 0;
length_base[28] = 258;