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.
 
 
 

199 lines
4.6 KiB

function readVint(buff: ArrayBuffer, start = 0) {
const buffer = new Uint8Array(buff);
const length = 8 - Math.floor(Math.log2(buffer[start]));
if (length > 8) {
throw new Error(`Unrepresentable length`);
}
if (start + length > buffer.length) {
return;
}
let value = buffer[start] & ((1 << (8 - length)) - 1);
for (let i = 1; i < length; i += 1) {
if (i === 7) {
if (value >= 2 ** 8 && buffer[start + 7] > 0) {
return { length, value: -1 };
}
}
value *= 2 ** 8;
value += buffer[start + i];
}
return { length, value };
}
enum DecoderState {
Tag, Size, Content
}
const schema = new Map([
[
0x1a45dfa3,
{
name: 'EBML',
type: 'm',
},
], [
0x1654ae6b,
{
name: 'Tracks',
type: 'm',
},
], [
0x4485,
{
name: 'TagBinary',
type: 'b',
},
],
[
0x4487,
{
name: 'TagString',
type: '8',
},
],
[
0x63c0,
{
name: 'Targets',
type: 'm',
},
],
[
0x7373,
{
name: 'Tag',
type: 'm',
},
],
[
0x1254c367,
{
name: 'Tags',
type: 'm',
},
],
[
0x45a3,
{
name: 'TagName',
type: '8',
},
],
[
0x67c8,
{
name: 'SimpleTag',
type: 'm',
},
]]);
type Tag = [id: number, type: string, name: string, ln: number, pos: number, size?: number]
export const decodeEBML = (arr: ArrayBuffer): {
tag: Tag;
isEnd?: true | undefined;
data?: ArrayBuffer | undefined;
}[] => {
let ptr = 0;
const buffer = new Uint8Array(arr);
const dw = new DataView(arr);
let total = 0;
let stack: Tag[] = [];
let ret: { tag: Tag, isEnd?: true, data?: ArrayBuffer }[] = [];
let state: number = DecoderState.Tag;
const readTag = () => {
if (ptr >= arr.byteLength)
return;
const vint = readVint(arr, ptr);
if (!vint)
return;
let v = 0;
switch (vint.length) {
case 1:
v = dw.getInt8(ptr);
break;
case 2:
v = dw.getInt16(ptr);
break;
case 3:
v = dw.getInt32(ptr) & 0xFFFFFF;
break;
case 4:
v = dw.getInt32(ptr);
break;
default:
throw "Unexpected v size";
}
const elem = schema.get(v);
const start = total;
total += vint.length;
const tag = [v, elem?.type, elem?.name, vint.length, start] as Tag;
stack.push(tag);
state = DecoderState.Size;
return true;
};
const readSize = () => {
const lasttag = stack.slice(-1)[0];
if (ptr >= arr.byteLength)
return;
const size = readVint(arr, ptr);
if (!size)
return;
ptr += size.length;
total += size.length;
lasttag[5] = size?.value;
if (lasttag[5] == -1)
lasttag[3] = -1;
else
lasttag[3] += size.length + size.value;
state = DecoderState.Content;
return true;
};
const readContent = () => {
const lasttag = stack.slice(-1)[0];
if (lasttag[1] == "m") {
state = DecoderState.Tag;
return true;
}
if (arr.byteLength < ptr + lasttag[5]!)
return;
const data = arr.slice(ptr, ptr + lasttag[5]!);
total += data.byteLength;
arr = arr.slice(ptr + lasttag[5]!);
ptr = 0;
state = DecoderState.Tag;
ret.push({ tag: lasttag, data });
stack.pop();
while (stack.length > 0) {
const top = stack.pop();
if (!top)
break;
if (total < top[4] + top[3]) {
stack.push(top);
break;
}
ret.push({ tag: lasttag, data, isEnd: true });
}
};
while (ptr < buffer.length) {
if (state == DecoderState.Tag) {
if (!readTag())
break;
} else if (state == DecoderState.Size) {
if (!readSize())
break;
} else if (state == DecoderState.Content) {
if (!readContent())
break;
}
}
return ret;
}