// ==UserScript==
// @name Diep.io Replay Beta
// @author A Happy peepo
// @namespace peepo
// @version 0.3.0
// @description Download And Watch Replays
// @match *://diep.io/
// @run-at document-start
// @license Apache License 2.0
// @require https://cdn.jsdelivr.net/gh/Qwokka/WAIL@41c655434da60f14dfda88813b0cef9000c79ca6/wail.js
// @grant none
// ==/UserScript==
"use strict";
WebAssembly.instantiateStreaming = (r, i) => r.arrayBuffer().then(b => WebAssembly.instantiate(b, i));
class PacketHook extends EventTarget {
static get CONST() {
return {
BUILD: "6ac60ba9d55769e868510c25366547d916b023b3",
RECV_PACKET_INDEX: 471,
MALLOC: "sa",
FREE: "X",
}
}
constructor(hook) {
super();
this.HEAPU8 = new Uint8Array(0);
this.HEAP32 = new Int32Array(0);
this.wasm = null;
this._inject(hook);
this._hijack();
}
_modify(bin, imports) {
console.log('Modifying WASM');
const wail = this.wail = new WailParser(new Uint8Array(bin));
const recvPacket = this.recvPacket = wail.getFunctionIndex(PacketHook.CONST.RECV_PACKET_INDEX);
const mainHook = wail.addImportEntry({
moduleStr: "hook",
fieldStr: "mainHook",
kind: "func",
type: wail.addTypeEntry({
form: "func",
params: ["i32", "i32"],
returnType: "i32"
})
});
wail.addExportEntry(recvPacket, {
fieldStr: "recvPacket",
kind: "func",
});
wail.addCodeElementParser(null, function({
index,
bytes
}) {
if (index === recvPacket.i32()) {
return new Uint8Array([
OP_GET_LOCAL, 0,
OP_GET_LOCAL, 1,
OP_CALL, ...VarUint32ToArray(mainHook.i32()),
OP_IF, VALUE_TYPE_BLOCK,
OP_RETURN,
OP_END,
...bytes
]);
}
return false;
});
wail.parse();
return wail.write();
}
_inject(mainHook) {
const _initWasm = WebAssembly.instantiate;
WebAssembly.instantiate = (bin, imports) => {
this.imports = {};
this.imports = Object.assign(this.imports, imports);
bin = this._modify(bin, imports);
imports.hook = {
mainHook
};
return _initWasm(bin, imports).then((wasm) => {
this.wasm = wasm.instance;
const memory = Object.values(this.wasm.exports).find(e => e instanceof WebAssembly.Memory);
this.HEAPU8 = new Uint8Array(memory.buffer);
this.HEAP32 = new Int32Array(memory.buffer);
this.malloc = this.wasm.exports[PacketHook.CONST.MALLOC];
this.free = this.wasm.exports[PacketHook.CONST.FREE];
console.log('Module exports done!\n\t- Hook.free\n\t- Hook.malloc\n\t- Hook.send\n\t- Hook.recv\n\t- Hook.addEventListener(\'clientbound\', ({data}) => console.log(data));\n\t- Hook.addEventListener(\'serverbound\', ({data}) => console.log(data));');
return wasm
}).catch(err => {
console.error('Error in loading up wasm:');
throw err;
})
};
}
_hijack() {
const that = this;
window.Object.defineProperty(Object.prototype, "postRun", {
get() {},
set(postRun) {
delete Object.prototype.postRun
this.postRun = postRun;
that.Module = this;
console.log('Module exports done! Hook.Module');
},
configurable: true,
});
}
recv(buf) {
const {
malloc,
free,
HEAP32,
HEAPU8
} = this;
buf = new Uint8Array(buf);
const ptr = malloc(buf.byteLength);
HEAPU8.set(buf, ptr);
this.wasm.exports.recvPacket(ptr, buf.byteLength)
free(ptr);
}
}
function ab2str(buf) {
return String.fromCharCode.apply(null, buf);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return new Uint8Array(buf);
}
Array.prototype.insert = function(index, item) {
this.splice(index, 0, item);
};
var buffer = [];
var replaylengths = [];
const Hook = window.Hook = new PacketHook(function(ptr, len) {
console.log(ptr,len);
if (Hook.HEAPU8[ptr] === 7 && !playback) {
buffer.insert(0, str2ab(PacketHook.CONST.BUILD));
replaylengths.insert(0, 0);
}
if (Hook.HEAPU8[ptr] === 2 || Hook.HEAPU8[ptr] === 10 || Hook.HEAPU8[ptr] === 11 || Hook.HEAPU8[ptr] === 13) return 0;
if (Hook.HEAPU8[ptr] >= 127) {
Hook.HEAPU8[ptr] -= 127;
return 0;
}
if (playback) return 1;
if (recording) {
let temp1 = new Uint32Array([len]);
let temp2 = new Float32Array([mouseX]);
let temp3 = new Float32Array([mouseY]);
let temp4 = Hook.HEAPU8.slice(ptr, ptr + len);
if (Hook.HEAPU8[ptr] === 0) replaylengths[0]++;
buffer[0] = concatenate(buffer[0], new Uint8Array(temp1.buffer), new Uint8Array(temp2.buffer), new Uint8Array(temp3.buffer), temp4)
}
});
var originmouseX = window.innerWidth / 2;
var originmouseY = window.innerHeight / 2;
let referenceWidth = window.innerWidth / 1920;
let referenceHeight = window.innerHeight / 1080;
var gamescale = referenceWidth < referenceHeight ? referenceHeight : referenceWidth;
function rezize() {
originmouseX = window.innerWidth / 2;
originmouseY = window.innerHeight / 2;
let referenceWidth = window.innerWidth / 1920;
let referenceHeight = window.innerHeight / 1080;
gamescale = referenceWidth < referenceHeight ? referenceHeight : referenceWidth;
}
addEventListener('resize', rezize);
var mouseX = originmouseX;
var mouseY = originmouseY;
function getcurrentpos(p) {
mouseX = p.pageX / window.innerWidth / gamescale;
mouseY = p.pageY / window.innerHeight / gamescale;
}
addEventListener('mousemove', getcurrentpos, false);
var recording = true;
var playback = false;
var btn = document.createElement("button");
btn.innerHTML = "Download Replay";
btn.style.zIndex = 1;
btn.style.position = "absolute";
var selection = null;
btn.onclick = function() {
if (buffer.length === 1) {
downloadbuffer(buffer[0]);
} else if (selection === null) {
var myParent = document.body;
//Create array of options to be added
var array = buffer.map((x, index) => "index:" + index + " length:" + (replaylengths[index] * 0.04) + " seconds")
//Create and append select list
var selectList = [];
var y = 50;
//Create and append the options
for (var i = 0; i < array.length; i++) {
var option = document.createElement("button");
option.value = i;
option.innerHTML = array[i];
option.style.zIndex = 1;
option.style.position = "absolute";
option.style.top = y + "px";
option.onclick = function() {
downloadbuffer(buffer[this.value]);
}
myParent.appendChild(option);
y += option.offsetHeight;
option.style.top = y + "px";
selectList.push(option);
}
selection = selectList;
return;
} else if (selection !== null) {
selection.forEach(x => x.remove())
selection = null;
}
};
var downloadbuffer = function(buffer) {
if (recording) {
const blob = new Blob([buffer], {
type: 'application/octet-stream'
})
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = "diep.io replay.bin"
document.body.appendChild(a)
a.style.display = 'none'
a.click()
a.remove()
setTimeout(() => window.URL.revokeObjectURL(url), 1000)
//btn.innerHTML = "Start Recording";
//buffer = concatenate(new Uint8Array([1, 0, 0, 0]), new Uint8Array([7]));
} // else {
//btn.innerHTML = "Stop Recording";
//}
//recording = !recording
}
document.body.appendChild(btn);
var input = document.createElement("input");
input.id = "file-input";
input.type = "file";
input.innerHTML = "Load Recording";
input.style.zIndex = 1;
input.style.left = "120px";
input.style.position = "absolute";
document.body.appendChild(input);
document.getElementById('file-input')
.addEventListener('change', readSingleFile, false);
function readSingleFile(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
var temp2 = new Uint8Array(contents);
playback = true;
var replay_id = ab2str(temp2.slice(0, 40));
var build_id = PacketHook.CONST.BUILD + '-' + replay_id;
var index = 40;
console.log("packet_id", replay_id);
WebSocket.prototype.send = function(data) {
if (playback) {
this.onclose = undefined;
this.onmessage = undefined;
this.onerror = undefined;
WebSocket.prototype.send = function() {};
return;
}
};
Object.defineProperty(WebSocket.prototype, 'readyState', {
get: function() {
return 1
}
});
var onload = function() {
window.input.set_convar("net_predict_movement", false);
document.getElementById('canvas').onmousemove = undefined;
const myInterval = setInterval(function() {
let length = (new Uint32Array(temp2.slice(index, index + 4).buffer))[0]
index += 4;
let mouseX = (new Float32Array(temp2.slice(index, index + 4).buffer))[0]
index += 4;
let mouseY = (new Float32Array(temp2.slice(index, index + 4).buffer))[0]
index += 4;
window['input']['mouse'](mouseX * window.innerWidth * gamescale, mouseY * window.innerHeight * gamescale);
if (temp2[index] === 2 || temp2[index] > 10) {
index += length;
return;
}
temp2[index] += 127;
Hook.recv(temp2.slice(index, index + length));
index += length;
if (index >= temp2.length) {
clearInterval(myInterval);
window.input.set_convar("net_predict_movement", true);
location.reload();
}
}, 40)
}
//here was wasm modifier, needs some work
onload();
};
reader.readAsArrayBuffer(file);
}
function concatenate(...arrays) {
// Calculate byteSize from all arrays
let size = arrays.reduce((a, b) => a + b.byteLength, 0)
// Allcolate a new buffer
let result = new Uint8Array(size)
// Build the new array
let offset = 0
for (let arr of arrays) {
result.set(arr, offset)
offset += arr.byteLength
}
return result
}