PEE/src/gif.ts

171 lines
5.3 KiB
TypeScript
Raw Normal View History

2022-01-03 22:29:28 +00:00
import { Buffer } from "buffer";
import type { EmbeddedFile, ImageProcessor } from "./main";
2022-01-04 15:36:43 +00:00
import { BufferWriteStream } from "./png";
2022-01-13 13:13:19 +00:00
import { decodeCoom3Payload, uploadFiles } from "./utils";
2022-01-03 22:29:28 +00:00
2022-01-04 15:36:43 +00:00
const netscape = Buffer.from("!\xFF\x0BNETSCAPE2.0", 'ascii');
2022-01-13 08:45:37 +00:00
const magic = Buffer.from("!\xFF\x0B" + "DOOMTECH1.1", 'ascii');
2022-07-09 10:26:18 +00:00
const magic2 = Buffer.from("!\xFF\x0B" + "VOOMTECH1.1", 'ascii');
2022-07-20 14:57:39 +00:00
const magic3 = Buffer.from("!\xFF\x0B" + "ANIMEXTS1.0", 'ascii');
2022-01-03 22:29:28 +00:00
const read_section = (gif: Buffer, pos: number) => {
const begin = pos;
pos += 3 + gif[pos + 2];
let buf = Buffer.alloc(0);
while (pos < gif.byteLength) {
const v = gif[pos++];
buf = Buffer.concat([buf, gif.slice(pos, pos + v)]);
if (v == 0)
break;
pos += v;
}
const appname = gif.slice(begin + 3, begin + 11).toString('ascii');
return {
appname,
data: buf,
end: pos
};
};
2022-07-09 10:26:18 +00:00
const password = Buffer.from("NOA");
const xor = (a: Buffer, p: Buffer) => {
let n = 0;
for (let i = 0; i < a.byteLength; ++i) {
a[i] ^= p[n];
n++;
n %= p.byteLength;
}
};
2022-01-03 22:29:28 +00:00
const extractBuff = (gif: Buffer) => {
2022-01-04 15:36:43 +00:00
const field = gif.readUInt8(10);
const gcte = !!(field & (1 << 7));
2022-01-03 22:29:28 +00:00
let end = 13;
if (gcte) {
end += 3 * (1 << ((field & 7) + 1));
}
// skip beeg blocks
while (gif[end] == '!'.charCodeAt(0)) {
let sec = read_section(gif, end); // this section contains the size to more easily preallocate a buffer size, but you don't need to care care
2022-01-13 13:13:19 +00:00
if (sec.appname == "DOOMTECH") {
const ret = Buffer.alloc(sec.data.readInt32LE(0));
let ptr = 0;
do {
sec = read_section(gif, sec.end);
sec.data.copy(ret, ptr);
ptr += sec.data.byteLength;
end = sec.end;
2022-07-09 10:26:18 +00:00
// v wtf did i mean by this
2022-01-13 13:13:19 +00:00
} while (sec.appname == "DOOMTECH" && gif[end] == '!'.charCodeAt(0));
return decodeCoom3Payload(ret);
2022-01-03 22:29:28 +00:00
}
2022-07-20 14:57:39 +00:00
const cond = () => sec.appname == "VOOMTECH" || sec.appname == 'ANIMEXTS';
if (cond()) {
2022-07-09 10:26:18 +00:00
const ret = Buffer.alloc(sec.data.readInt32LE(0));
let ptr = 0;
do {
sec = read_section(gif, sec.end);
sec.data.copy(ret, ptr);
ptr += sec.data.byteLength;
end = sec.end;
2022-07-20 14:57:39 +00:00
} while (cond() && gif[end] == '!'.charCodeAt(0));
2022-07-09 10:26:18 +00:00
xor(ret, password);
return decodeCoom3Payload(ret);
}
end = sec.end;
2022-01-03 22:29:28 +00:00
}
2022-04-11 11:19:43 +00:00
throw new Error("Shouldn't happen");
2022-01-03 22:29:28 +00:00
// metadata ended, nothing...
};
2022-01-05 01:14:23 +00:00
const extract = extractBuff;
2022-01-03 22:29:28 +00:00
const write_data = async (writer: WritableStreamDefaultWriter<Buffer>, inj: Buffer) => {
2022-07-20 14:57:39 +00:00
await writer.write(magic3);
2022-01-03 22:29:28 +00:00
const byte = Buffer.from([0]);
let size = inj.byteLength;
let ws;
let offset = 0;
while (size != 0) {
ws = size >= 255 ? 255 : size;
byte.writeUInt8(ws, 0);
await writer.write(byte);
await writer.write(inj.slice(offset, offset + ws));
size -= ws;
offset += ws;
}
byte.writeUInt8(0, 0);
await writer.write(byte);
};
const write_embedding = async (writer: WritableStreamDefaultWriter<Buffer>, inj: Buffer) => {
const b = Buffer.alloc(4);
b.writeInt32LE(inj.byteLength, 0);
await write_data(writer, b);
let size = inj.byteLength;
let offset = 0;
while (size != 0) {
const ws = size >= (3 << 13) ? (3 << 13) : size;
await write_data(writer, inj.slice(offset, offset + ws));
offset += ws;
size -= ws;
}
};
const inject = async (container: File, links: string[]) => {
2022-01-03 22:29:28 +00:00
const [writestream, extract] = BufferWriteStream();
const writer = writestream.getWriter();
2022-01-13 08:45:37 +00:00
const inj = Buffer.from(links.join(' '));
2022-07-09 10:26:18 +00:00
xor(inj, password);
2022-01-04 15:36:43 +00:00
const contbuff = Buffer.from(await container.arrayBuffer());
2022-01-03 22:29:28 +00:00
2022-01-04 15:36:43 +00:00
const field = contbuff.readUInt8(10);
const gcte = !!(field & (1 << 0x7));
2022-01-03 22:29:28 +00:00
let endo = 13;
if (gcte)
endo += 3 * (1 << ((field & 7) + 1));
if (netscape.compare(contbuff, endo, endo + netscape.byteLength) == 0)
2022-01-06 00:10:12 +00:00
endo += 19;
2022-01-03 22:29:28 +00:00
await writer.write(contbuff.slice(0, endo));
2022-01-13 08:45:37 +00:00
await write_embedding(writer, Buffer.from(inj));
2022-01-03 22:29:28 +00:00
await writer.write(contbuff.slice(endo));
return extract();
2022-01-04 15:36:43 +00:00
};
2022-01-05 01:14:23 +00:00
const has_embed = (gif: Buffer) => {
2022-01-04 15:36:43 +00:00
const field = gif.readUInt8(10);
const gcte = !!(field & (1 << 7));
let end = 13;
if (gcte) {
end += 3 * (1 << ((field & 7) + 1));
}
// skip beeg blocks
while (end < gif.byteLength && gif.readUInt8(end) == '!'.charCodeAt(0)) {
2022-07-20 14:57:39 +00:00
if ([magic, magic2, magic3].every(m => m.compare(gif, end, end + m.byteLength) != 0)) {
2022-01-04 15:36:43 +00:00
end += 3 + gif.readUInt8(end + 2);
// eslint-disable-next-line no-constant-condition
while (true) { // skip sub blocks
const v = gif.readUInt8(end++);
if (!v)
break;
end += v;
}
} else {
return true;
}
}
if (end >= gif.byteLength)
return; // Don't know yet, need more to decide.
return false; // no more extension blocks, so definite no
2022-01-05 01:14:23 +00:00
};
export default {
extract,
has_embed,
2022-01-13 08:45:37 +00:00
inject,
2022-01-05 01:14:23 +00:00
match: fn => !!fn.match(/\.gif$/)
} as ImageProcessor;