Browse Source

Performance and stability fixes for PNGs.

pull/46/head
coomdev 2 years ago
parent
commit
d7bd55dff6
  1. 2
      README.md
  2. 5265
      chrome/dist/main.js
  3. 2
      chrome/manifest.json
  4. 5265
      dist/main.js
  5. 5264
      firefox/dist/main.js
  6. 2
      firefox/manifest.json
  7. 2
      firefox_update.json
  8. 2
      main.meta.js
  9. 5267
      main.user.js
  10. 1039
      package-lock.json
  11. 14
      package.json
  12. BIN
      pngextraembedder-0.287.xpi
  13. 168
      src/bitstream.ts
  14. 245
      src/dh-deflate.ts
  15. 1
      src/main.ts
  16. 26
      src/png.ts
  17. 30
      src/pngv3.ts

2
README.md

@ -25,7 +25,7 @@ Please report any issue you have with those (only for mainstream browsers)
Also, use this if you plan to use b4k's archive.
- [Install 4chanX (recommended)](https://www.4chan-x.net/builds/4chan-X.user.js)
- Install the correct WebExtension for your Browser ([Firefox](https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.285.xpi) or Chrome-based (Down for "maintainance"))
- Install the correct WebExtension for your Browser ([Firefox](https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.287.xpi) or Chrome-based (Down for "maintainance"))
For FF users, the extension is signed so you can just drag and drop it on your about:addons tab.

5265
chrome/dist/main.js

File diff suppressed because it is too large

2
chrome/manifest.json

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "PngExtraEmbedder",
"description": "Discover embedded files on 4chan and archives!",
"version": "0.286",
"version": "0.287",
"icons": {
"64": "1449696017588.png"
},

5265
dist/main.js

File diff suppressed because it is too large

5264
firefox/dist/main.js

File diff suppressed because it is too large

2
firefox/manifest.json

@ -7,7 +7,7 @@
},
"name": "PngExtraEmbedder",
"description": "Discover embedded files on 4chan and archives!",
"version": "0.285",
"version": "0.287",
"icons": {
"64": "1449696017588.png"
},

2
firefox_update.json

@ -1 +1 @@
{"addons":{"{34ac4994-07f2-44d2-8599-682516a6c6a6}":{"updates":[{"version":"0.285","update_link":"https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.285.xpi"}]}}}
{"addons":{"{34ac4994-07f2-44d2-8599-682516a6c6a6}":{"updates":[{"version":"0.287","update_link":"https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.287.xpi"}]}}}

2
main.meta.js

@ -1,7 +1,7 @@
// ==UserScript==
// @name PNGExtraEmbed
// @namespace https://coom.tech/
// @version 0.286
// @version 0.287
// @description uhh
// @author You
// @match https://boards.4channel.org/*

5267
main.user.js

File diff suppressed because it is too large

1039
package-lock.json

File diff suppressed because it is too large

14
package.json

@ -16,8 +16,11 @@
"license": "ISC",
"dependencies": {
"@astronautlabs/bitstream": "^4.1.2",
"assert-browserify": "^2.0.0",
"base58": "^2.0.1",
"blockhash": "^0.2.0",
"browser-process": "^0.0.1",
"browserify-zlib": "^0.2.0",
"bs58": "^5.0.0",
"buffer": "^6.0.3",
"crc-32": "^1.2.0",
@ -31,10 +34,12 @@
"linkify-string": "^3.0.4",
"lodash": "^4.17.21",
"mp4box": "^0.5.2",
"path-browserify": "^1.0.1",
"png-js": "^1.0.0",
"readable-stream": "^3.6.0",
"socks-proxy-agent": "^7.0.0",
"ts-ebml": "^2.0.2"
"ts-ebml": "^2.0.2",
"util": "^0.12.4"
},
"devDependencies": {
"@tsconfig/svelte": "^3.0.0",
@ -56,6 +61,11 @@
},
"browser": {
"node:buffer": "buffer",
"node:stream": "readable-stream"
"node:stream": "readable-stream",
"stream": "readable-stream",
"assert": "assert-browserify",
"path": "assert-browserify",
"process": "browser-process",
"zlib": "browserify-zlib"
}
}

BIN
pngextraembedder-0.287.xpi

Binary file not shown.

168
src/bitstream.ts

@ -13,20 +13,74 @@ export const revbyte = (n: number, len = 8) => {
return acc;
};
export class BitstreamReader extends br {
rtotal = 0;
export class BitstreamReader {
private buffers: any[] = [];
addBuffer(hidden: Buffer) {
const inp = Buffer.from(hidden);
for (let i = 0; i < inp.byteLength; ++i)
inp[i] = revbyte(inp[i]);
super.addBuffer(inp);
private bufferedLength = 0;
private _offsetIntoBuffer = 0;
private _bufferIndex = 0;
private _offset = 0;
/**
* Get the index of the buffer currently being read. This will always be zero unless retainBuffers=true
*/
get bufferIndex() {
return this._bufferIndex;
}
readSync(len: number) {
const v = super.readSync(len);
this.rtotal += len;
return revbyte(v, len);
/**
* Get the current offset in bits, starting from the very first bit read by this reader (across all
* buffers added)
*/
get offset() {
return this._offset;
}
/**
* The number of bits that are currently available.
*/
get available() {
return this.bufferedLength - this.skippedLength;
}
private skippedLength = 0;
/**
* Read an unsigned integer of the given bit length synchronously. If there are not enough
* bits available, an error is thrown.
*
* @param length The number of bits to read
* @returns The unsigned integer that was read
*/
getBit(offset: number) {
const byte = this.buffers[0][offset >> 3];
return +!!(byte & (1 << (offset & 7)));
}
readSync(length: number): number {
let value = 0;
//console.log(this.buffers[0])
const byte = this.buffers[0][this._offset >> 3];
for (let i = length - 1; i >= 0; --i) {
value = value * 2 + this.getBit(this._offset + i);
}
this._offset += length;
this.bufferedLength -= length;
return value;
}
/**
* Add a buffer onto the end of the bitstream.
* @param buffer The buffer to add to the bitstream
*/
addBuffer(buffer: Uint8Array) {
this.buffers.push(buffer);
this.bufferedLength += buffer.length * 8;
}
}
@ -34,26 +88,76 @@ export type Writable = {
write: (chunk: Buffer) => void;
};
export class BitstreamWriter extends bw {
wtotal = 0;
constructor(private w: Writable) {
super({
write: (c) => {
const inp = Buffer.from(c);
for (let i = 0; i < inp.byteLength; ++i)
inp[i] = revbyte(inp[i]);
this.w.write(inp);
}
});
}
write(length: number, value: number): void {
this.wtotal += length;
if (length) {
//tconsole.log(length, value)
value = revbyte(value, length);
export class BitstreamWriter {
/**
* Create a new writer
* @param stream The writable stream to write to
* @param bufferSize The number of bytes to buffer before flushing onto the writable
*/
constructor(public stream: Writable, bufferSize = 1) {
bufferSize = 1;
this.buffer = new Uint8Array(bufferSize);
}
private pendingBits = 0;
private buffer: Uint8Array;
bufferoffset = 0;
private _offset = 0;
/**
* How many bits have been written via this writer in total
*/
get offset() {
return this._offset;
}
/**
* How many bits into the current byte is the write cursor.
* If this value is zero, then we are currently byte-aligned.
* A value of 7 means we are 1 bit away from the byte boundary.
*/
get byteOffset() {
return this.pendingBits;
}
/**
* Finish the current byte (assuming zeros for the remaining bits, if necessary)
* and flushes the output.
*/
end() {
//this.finishByte();
this.flush();
}
flush() {
this.stream.write(Buffer.from(this.buffer));
this.bufferoffset = 0;
this.buffer.fill(0);
}
setBit(b: number) {
let byte = this.buffer[0];
byte |= b << (this._offset & 7);
this.buffer[0] = byte;
this._offset += 1;
if (++this.bufferoffset == this.buffer.length * 8) {
this.flush();
}
super.write(length, value);
}
}
/**
* Write the given number to the bitstream with the given bitlength. If the number is too large for the
* number of bits specified, the lower-order bits are written and the higher-order bits are ignored.
* @param length The number of bits to write
* @param value The number to write
*/
write(length: number, value: number) {
while (length--) {
this.setBit(value & 1);
value >>= 1;
}
}
}

245
src/dh-deflate.ts

@ -5,15 +5,23 @@ 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)?];
let log = (x, ...n) => {
//console.log('t2', x, ...n);
}
// 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 => {
let pathMap = new Map<HCtree, Map<number, {
length: number,
val: number
}>>();
let getPathTo = (tree: HCtree, value: number): string | undefined => {
if (tree[0] === value)
return '0';
if (tree[1] === value)
@ -35,7 +43,6 @@ const getPathTo = (tree: HCtree, value: number): string | undefined => {
// 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--;
@ -77,11 +84,9 @@ class Data {
}
ltree: Tree;
dtree: Tree;
rltree!: HCtree;
rdtree!: HCtree;
adists!: Set<number>;
@ -98,34 +103,31 @@ class Data {
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
const sltree = new Tree();
const sdtree = new Tree();
let sltree = new Tree();
let 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);
let length_bits = new Uint8Array(30);
let 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);
let dist_bits = new Uint8Array(30);
let dist_base = new Uint16Array(30);
/* special ordering of code length codes */
const clcidx = new Uint8Array([
let 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);
let code_tree = new Tree();
let lengths = new Uint8Array(288 + 32);
/* ----------------------- *
* -- utility functions -- *
@ -171,7 +173,7 @@ function tinf_build_fixed_trees(lt: Tree, dt: Tree) {
}
/* given an array of code lengths, build a tree */
const offs = new Uint16Array(16);
let offs = new Uint16Array(16);
function tinf_build_tree(t: Tree, lengths: Uint8Array, off: number, num: number) {
let i, sum;
@ -202,15 +204,25 @@ function tinf_build_tree(t: Tree, lengths: Uint8Array, off: number, num: number)
/* get one bit from source stream */
function tinf_getbit(d: Data) {
const v = d.source.readSync(1);
let v = d.source.readSync(1);
log(v, 'getbit');
return v;
}
let loff = 0;
let 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;
let v = d.source.readSync(num) + base;
loff = v;
//console.log(v);
loffs.push(v);
if (loffs.length > 4) {
loffs.shift();
}
return v;
}
@ -221,7 +233,7 @@ function tinf_decode_symbol(d: Data, t: Tree, copy = true, ext: any = {}) {
/* get more bits while code value is above sum */
let s = 0;
do {
const b = d.source.readSync(1);
let b = d.source.readSync(1);
copy && d.hidden.write(1, b);
s = (s << 1) | b;
cur = 2 * cur + b;
@ -235,29 +247,45 @@ function tinf_decode_symbol(d: Data, t: Tree, copy = true, ext: any = {}) {
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 {
let 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) {
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);
d.hidden?.write(5, hlit - 257);
let 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);
d.hidden?.write(5, hdist - 1);
let 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);
d.hidden?.write(4, hclen - 4);
let 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);
d.hidden?.write(3, clen);
let clen = tinf_read_bits(d, 3, 0);
copy && d.hidden?.write(3, clen);
lengths[clcidx[i]] = clen;
}
@ -267,14 +295,14 @@ function tinf_decode_trees(d: Data, lt: Tree, dt: Tree) {
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist;) {
const sym = tinf_decode_symbol(d, code_tree);
let prev: number;
let sym = tinf_decode_symbol(d, code_tree, copy);
switch (sym) {
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
prev = lengths[num - 1];
let prev = lengths[num - 1];
length = tinf_read_bits(d, 2, 3);
d.hidden?.write(2, length - 3);
copy && d.hidden?.write(2, length - 3);
for (; length; --length) {
lengths[num++] = prev;
}
@ -282,7 +310,7 @@ function tinf_decode_trees(d: Data, lt: Tree, dt: Tree) {
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
length = tinf_read_bits(d, 3, 3);
d.hidden?.write(3, length - 3);
copy && d.hidden?.write(3, length - 3);
for (; length; --length) {
lengths[num++] = 0;
}
@ -290,7 +318,7 @@ function tinf_decode_trees(d: Data, lt: Tree, dt: Tree) {
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
length = tinf_read_bits(d, 7, 11);
d.hidden?.write(7, length - 11);
copy && d.hidden?.write(7, length - 11);
for (; length; --length) {
lengths[num++] = 0;
}
@ -307,51 +335,46 @@ function tinf_decode_trees(d: Data, lt: Tree, dt: Tree) {
tinf_build_tree(dt, lengths, hlit, hdist);
}
const bufferEq = (a: Uint8Array, b: Uint8Array) => {
// this is assumed
// if (a.byteLength != b.byteLength)
// return false;
for (let i = 0; i < a.byteLength; ++i)
if (a[i] != b[i])
return i;
return -1;
};
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]];
return [i, bits_table[i], value - base_table[i]]
}
}
i--;
return [i, bits_table[i], value - base_table[i]];
return [i, bits_table[i], value - base_table[i]]
};
const encode_symbol = (sym: number, tree: HCtree) => {
let m: Map<number, {length: number, val: number}> | undefined;
if ((m = pathMap.get(tree))) {
let v = m.get(sym);
if (v) return v;
} else {
m = new Map;
pathMap.set(tree, m);
}
const code = getPathTo(tree, sym)!;
return {
let 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) {
let finished = false;
// eslint-disable-next-line no-constant-condition
while (1) {
if (finished) {
return true;
}
let sym = tinf_decode_symbol(d, lt); // copy
/* check for end of block */
@ -363,28 +386,31 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
d.dest.push(sym);
// same
} else {
let length, dist, offs;
sym -= 257;
/* possibly get more bits from length code */
const length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
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
let ext = { length: 0, sym: 0 };
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;
const match = Buffer.from(d.dest.slice(offs, offs + length));
offs = d.dest.length - backoffset;
let match: Buffer;
// don't consider matches that could be in the lookahead buffer
if (match.length == length) {
let skip = 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;
@ -392,7 +418,7 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
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);
let r = slic.slice(o, d.dest.length).indexOf(match);
if (r >= 0) {
matches.push(r + begin + o);
o += r;
@ -406,7 +432,7 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
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;
@ -416,8 +442,6 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
if (d.to_hide.available) {
const s = d.to_hide.readSync(Math.min(d.to_hide.available, v));
backoffset = matches[s];
} else {
finished = true;
}
}
// extract hidden bit
@ -432,15 +456,12 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
//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);
let [dsym, dlen, doff] = get_symbol(backoffset, dist_bits, dist_base);
let encdist = encode_symbol(dsym, d.rdtree);
if (isNaN(encdist.val)) {
debugger;
encdist = encode_symbol(dsym, d.rdtree);
}
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]);
@ -451,17 +472,23 @@ function tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree) {
/* inflate an uncompressed block of data */
function tinf_inflate_uncompressed_block(d: Data) {
/* get length */
const length = d.source.readSync(16);
if (d.source.offset & 7)
d.source.readSync(8 - d.source.offset & 7);
if (d.hidden.offset & 7)
d.hidden.write(8 - d.hidden.offset & 7, 0);
let length = d.source.readSync(16);
d.hidden.write(16, length);
/* get one's complement of length */
const invlength = d.source.readSync(16);
let invlength = d.source.readSync(16);
d.hidden.write(16, invlength);
/* check length */
if (length !== (~invlength & 0x0000ffff))
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);
@ -470,6 +497,49 @@ function tinf_inflate_uncompressed_block(d: Data) {
return TINF_OK;
}
function copy_block_data(d: Data, lt: Tree, dt: Tree) {
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 {
let length, dist, offs;
sym -= 257;
length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
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.offset & 7)
d.hidden.write(8 - d.hidden.offset & 7, 0);
let length: number;
d.hidden.write(16, length = d.source.readSync(16));
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
@ -477,28 +547,16 @@ export function tinf_uncompress(source: BitstreamReader,
// stream of data to hide
to_hide: BitstreamReader | BitstreamWriter,
// compressed stream containing hidden data
hidden: (chunk: Uint8Array) => void) {
hidden: (chunk: Uint8Array) => void, opt = 0) {
const decomp = new BitstreamWriter({ write: (decompressed || (() => {/** */ })) });
const hid = new BitstreamWriter({ write: (hidden || (() => {/** */ })) });
let decomp = new BitstreamWriter({ write: (decompressed || (() => { })) });
let hid = new BitstreamWriter({ write: (hidden || (() => { })) }, 4);
const d = new Data(source, decomp, to_hide, hid);
let d = new Data(source, decomp, to_hide, hid);
let res: number | undefined | true;
let bfinal: number, btype: number;
do {
if (to_hide instanceof BitstreamReader) {
if (to_hide.available == 0) {
// copy until we're byte-aligned
while (source.available) {
const r = 1;
hid.write(r, source.readSync(r));
}
// nothing left to embed, we are byte aligned, so we just "memcpy" the rest
return source.offset >> 3; // this is block aligned, so this doesn't tell us where the last hidden bit is, just an upper bound
}
}
// TODO: truncate to_hide if we get close to 70k?
/* read final block flag */
@ -522,16 +580,12 @@ export function tinf_uncompress(source: BitstreamReader,
d.rltree = rltree;
d.adists = sadist;
res = tinf_inflate_block_data(d, sltree, sdtree);
if (res === true)
continue;
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);
if (res === true)
continue;
break;
default:
res = -2;
@ -543,7 +597,8 @@ export function tinf_uncompress(source: BitstreamReader,
} while (!bfinal);
decomp.end();
hid.end();
hid.end()
//if (d.dest.byteOffset < d.dest.length) {
// if (typeof d.dest.slice === 'function')

1
src/main.ts

@ -1,3 +1,4 @@
import 'process';
import { Buffer } from "buffer";
import { appState, settings, initial_settings } from "./stores";
import { debounce } from './debounce';

26
src/png.ts

@ -4,8 +4,8 @@ import type { ImageProcessor } from "./main";
export type PNGChunk = [
string, // name
() => Promise<Buffer>, // data
() => Promise<number>, // crc
Buffer, // data
number, // crc
number];// offset
export class PNGDecoder {
@ -41,19 +41,13 @@ export class PNGDecoder {
if (this.stopped) break;
const length = this.repr.readUInt32BE(this.ptr);
const name = this.repr.slice(this.ptr + 4, this.ptr + 8).toString();
this.ptr += 4;
this.ptr += 4; // set pointer to data;
this.req += length + 4; // crc
//await this.catchup();
const pos = this.ptr;
await this.catchup(); // try to get entire chunk
// allow last chunk to be partial
yield [name,
async () => {
await this.catchup();
return this.repr.slice(pos, pos + length + 4);
},
async () => {
await this.catchup();
return this.repr.readUInt32BE(this.ptr + length + 4);
},
this.repr.slice(this.ptr, this.ptr + length + 4),
this.ptr + length > this.repr.length ? -1 : this.repr.readUInt32BE(this.ptr + length + 4),
this.ptr] as PNGChunk;
if (this.stopped) break;
this.ptr += length + 8;
@ -77,7 +71,7 @@ export class PNGEncoder {
async insertchunk(chunk: PNGChunk) {
let b = Buffer.alloc(4);
const buff = await chunk[1]();
const buff = chunk[1];
b.writeInt32BE(buff.length - 4, 0);
await this.writer.write(b); // write length
await this.writer.write(buff); // chunk includes chunkname
@ -87,8 +81,8 @@ export class PNGEncoder {
}
async dtor() {
//this.writer.releaseLock();
await this.writer.close();
this.writer.releaseLock();
//await this.writer.close();
}
}

30
src/pngv3.ts

@ -157,7 +157,7 @@ const extract = async (png: Buffer) => {
switch (name) {
// should exist at the beginning of file to signal decoders if the file indeed has an embedded chunk
case 'tEXt':
buff = await chunk();
buff = chunk;
if (buff.slice(4, 4 + CUM3.length).equals(CUM3)) {
const k = await decodeCoom3Payload(buff.slice(4 + CUM3.length));
ret.push(...k.filter(e => e).map(e => e as EmbeddedFile));
@ -206,9 +206,10 @@ const extract = async (png: Buffer) => {
case 'IDAT':
if (ret.length)
return ret;
buff = await chunk();
buff = chunk;
idats.push(buff.slice(4));
break;
// eslint-disable-next-line no-fallthrough
case 'IEND':
complete = true;
@ -270,6 +271,8 @@ const embedInRawDeflate = (b: Buffer, h: Buffer) => {
};
export const inject_data = async (container: File, injb: Buffer) => {
//injb = Buffer.from("https://take-me-to.space/6C1a6s5.pee");
if (!csettings)
throw new Error("Settings uninit");
if (csettings.pmeth < 5) {
@ -285,20 +288,20 @@ export const inject_data = async (container: File, injb: Buffer) => {
const passed = Buffer.from(injb);
switch (csettings.pmeth) {
case 0:
encoder.insertchunk(["tEXt", () => Promise.resolve(buildChunk("tEXt", Buffer.concat([CUM3, passed]))), () => Promise.resolve(0), 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", Buffer.concat([CUM3, passed])), 0, 0]);
break;
case 1:
xor(passed, password);
encoder.insertchunk(["tEXt", () => Promise.resolve(buildChunk("tEXt", Buffer.concat([CUM4, Buffer.from(Buffer.from(passed).toString("base64"))]))), () => Promise.resolve(0), 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", Buffer.concat([CUM4, Buffer.from(Buffer.from(passed).toString("base64"))])), 0, 0]);
break;
case 2:
encoder.insertchunk(["tEXt", () => Promise.resolve(buildChunk("tEXt", Buffer.concat([CUM5, Buffer.from(Buffer.from(passed).toString("base64"))]))), () => Promise.resolve(0), 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", Buffer.concat([CUM5, Buffer.from(Buffer.from(passed).toString("base64"))])), 0, 0]);
break;
case 3:
encoder.insertchunk(["tEXt", () => Promise.resolve(buildChunk("tEXt", Buffer.concat([CUM6, Buffer.from(Buffer.from(passed).toString("base64"))]))), () => Promise.resolve(0), 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", Buffer.concat([CUM6, Buffer.from(Buffer.from(passed).toString("base64"))])), 0, 0]);
break;
case 4:
encoder.insertchunk(["tEXt", () => Promise.resolve(buildChunk("tEXt", Buffer.concat([CUM7, Buffer.from(bs58.encode(passed))]))), () => Promise.resolve(0), 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", Buffer.concat([CUM7, Buffer.from(bs58.encode(passed))])), 0, 0]);
break;
}
magic = true;
@ -306,8 +309,8 @@ export const inject_data = async (container: File, injb: Buffer) => {
encoder.insertchunk([name, chunk, crc, offset]);
}
encoder.insertchunk(["IEND",
() => Promise.resolve(buildChunk("IEND", Buffer.from([]))),
() => Promise.resolve(0),
buildChunk("IEND", Buffer.from([])),
0,
0]);
return extract();
}
@ -316,21 +319,24 @@ export const inject_data = async (container: File, injb: Buffer) => {
const concat: Buffer[] = [];
for await (const chk of pdec.chunks())
if (chk[0] == "IDAT")
concat.push((await chk[1]()).slice(4));
concat.push(chk[1].slice(4));
const comp = Buffer.concat(concat);
const head = comp.slice(0, 2); // keep the header the same
const chksum = comp.slice(-4); // checksum is over the uncompressed data, so no need to recalculate
//const orig = zlib.inflateRawSync(comp.slice(2, -4));
const idatblk = embedInRawDeflate(comp.slice(2, -4), injb);
//const norig = zlib.inflateRawSync(idatblk);
//console.log('diff', orig.compare(norig));
const [writestream, extract] = BufferWriteStream();
const penc = new PNGEncoder(writestream);
pdec = new PNGDecoder(container.stream().getReader()); // restart again
let ins = false;
for await (const chk of pdec.chunks()) {
if (chk[0] != "IDAT") {
penc.insertchunk(chk);
await penc.insertchunk(chk);
} else {
if (!ins) {
penc.insertchunk(["IDAT", () => Promise.resolve(Buffer.concat([Buffer.from('IDAT'), head, idatblk, chksum])), () => Promise.resolve(0), 0]);
await penc.insertchunk(["IDAT", Buffer.concat([Buffer.from('IDAT'), head, idatblk, chksum]), 0, 0]);
ins = true;
}
}

Loading…
Cancel
Save