// ==UserScript==
// @name Io Record
// @namespace http://tampermonkey.net/
// @version 0.0.8
// @author Big watermelon
// @description This script is in beta testing !! Record any io game (agma.io only for now)
// @match *://agma.io/*
// @license All Rights Reserved
// @icon data:image/png;base64,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
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
/*
Copyright © 2024 Big watermelon. All Rights Reserved.
This work is proprietary and may not be copied, distributed, or modified without explicit permission.
*/
// FIXIT: no death screen
// FIXIT: gets disconnected when close window
(function() {
'use strict';
if (unsafeWindow.top !== unsafeWindow.self || document.querySelector('title')?.textContent?.includes('Just a moment'))
return;
const settings = {
toggleKey: 'm',
toggleMouseLock: '"',
recordAnimations: false,
recordLeaderboard: false,
recordMovingBorders: true,
autoStopAfter: 0, // seconds
fetchChangeLog: true
};
function serializeArray(recordData) {
return recordData.map(frame => Math.round(frame.offset) + ':' + btoa(String.fromCharCode(...new Uint8Array(frame.data)))).join("|");
}
function deserializeString(recordData) {
return recordData.split("|").map(frame => {
const [offset, base64Data] = frame.split(":");
const binaryString = atob(base64Data);
const buffer = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++)
buffer[i] = binaryString.charCodeAt(i);
return { id: buffer[0], offset: Number(offset), data: buffer.buffer };
});
}
const savedRecords = GM_getValue('records', []),
toggleMouseCode = JSON.parse(localStorage.hotkeys).C.c;
const MANDATORY_PACKETS = [10, 11, 12, 20, 32, 33, 48, 49, 50, 65, 66],
CLEAR_ALL = { data: new Uint8Array([20]).buffer },
FAKE_CELL_UPDATE = { data: new Uint8Array([10, 0, 0, 0, 0, 0, 0]).buffer };
savedRecords.forEach(record => record.data = deserializeString(record.data))
let currentRecord = null,
clipPlayer = null,
start,
recordButton,
isPaused = true,
viewedRecord = null,
isMouseLocked = false,
wsOnmessage,
frameIndex = 0,
menuDom = document.createElement('html');
menuDom.innerHTML = `
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Io Record</title>
<style>
body {
width: 100vh;
height: 100vh;
background-color: #373737;
overflow: hidden;
}
svg {
cursor: pointer;
}
span {
background: #6f6f6f;
padding: 6px 12px;
text-align: center;
font-weight: bold;
border-radius: 10px;
margin: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div style="max-height: calc(100% - 70px); overflow: hidden visible; display: flex; width: 100%; position: absolute; left: 0; justify-content: center; flex-direction: column;">
${savedRecords.map(record => '<span>' + record.name + '</span>').join('')}
</div>
<div style="display: flex; position: absolute; bottom: 12px; left: 50%; transform: translate(-50%, 0);">
<svg title="Previous frame" width="50" height="50" viewBox="0 0 24 24">
<polygon points="19,5 9,12 19,19"/>
<rect x="5" y="5" width="2" height="14"/>
</svg>
<svg title="Play" width="50" height="50" viewBox="0 0 24 24">
<polygon points="8,5 19,12 8,19"/>
</svg>
<svg title="Pause" style="display:none;" width="50" height="50" viewBox="0 0 24 24">
<rect x="6" y="5" width="4" height="14"/>
<rect x="14" y="5" width="4" height="14"/>
</svg>
<svg title="Next frame" width="50" height="50" viewBox="0 0 24 24">
<polygon points="5,5 15,12 5,19"/>
<rect x="17" y="5" width="2" height="14"/>
</svg>
<svg title="Download clip" width="50" height="50" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 16L12 4M8 12L12 16L16 12" stroke="currentColor" stroke-width="2" fill="none"/>
<line x1="4" y1="20" x2="20" y2="20" stroke="currentColor" stroke-width="2" />
</svg>
</div>
<input type="range" value="0" min="0" max="20000" style="width: calc(100% - 50px); position: absolute; bottom: 0; left: 0;">
<p style="font-size: 18px; margin: 0; width: 40px; position: absolute; bottom: 0; right: 0;">0s</p>
</body>
`;
const clipsList = menuDom.lastElementChild.firstElementChild;
const [previousFrameButton, playButton, pauseButton, nextFrameButton, downloadButton, timeControlInput, timeText] = menuDom.querySelectorAll('svg, input, p');
playButton.onclick = pauseButton.onclick = () => {
if (viewedRecord == null) return;
if (isPaused) {
isPaused = false;
goto();
} else {
isPaused = true;
}
updateButtonsStyle();
};
previousFrameButton.onclick = () => {
// This feature is glitchy
/*
if (viewedRecord == null || !frameIndex) return;
isPaused = true;
const recordData = savedRecords[viewedRecord].data;
let current;
do {
wsOnmessage(current = recordData[frameIndex--]);
} while (frameIndex && current.id != 10);
updateButtonsStyle();
timeText.innerText = Math.round((timeControlInput.max - (timeControlInput.value = recordData[frameIndex].offset)) / 1000) + 's';
*/
};
nextFrameButton.onclick = () => {
const recordData = savedRecords[viewedRecord].data;
if (viewedRecord == null || frameIndex >= recordData.length) return;
isPaused = true;
let current;
do {
wsOnmessage(current = recordData[frameIndex++]);
} while (frameIndex <= recordData.length && current.id != 10);
updateButtonsStyle();
timeText.innerText = Math.round((timeControlInput.max - (timeControlInput.value = recordData[frameIndex].offset)) / 1000) + 's';
};
timeControlInput.onchange = () => {
if (viewedRecord == null) return;
timeText.innerText = Math.round((timeControlInput.max - timeControlInput.value) / 1000) + 's';
const recordData = savedRecords[viewedRecord].data;
isPaused = true;
frameIndex = 0;
wsOnmessage(CLEAR_ALL);
let current;
do {
wsOnmessage(current = recordData[frameIndex++]);
} while (current.offset < timeControlInput.value);
isPaused = true;
updateButtonsStyle();
};
clipsList.onclick = ({ target }) => {
const clips = Array.from(clipsList.children);
const i = clips.indexOf(target);
isPaused = true;
if (viewedRecord != null)
clips[viewedRecord].style.border = '';
if (target.tagName != 'SPAN' || viewedRecord == i) {
viewedRecord = null;
return;
}
frameIndex = 0;
wsOnmessage(CLEAR_ALL);
target.style.border = '1px solid white';
const recordData = savedRecords[viewedRecord = i].data;
timeControlInput.max = recordData[recordData.length - 1].offset;
timeControlInput.value = 0;
timeText.innerText = Math.round(timeControlInput.max / 1000) + 's';
};
const dropZone = menuDom.lastElementChild;
dropZone.ondragenter = dropZone.ondragover = dropZone.ondragleave = event => event.preventDefault();
dropZone.addEventListener('drop', event => {
event.preventDefault();
const file = event.dataTransfer.files[0];
if (file) {
const reader = new FileReader();
reader.onload = e => {
const d = new Date();
savedRecords.push({ name: file.name, data: deserializeString(e.target.result) });
const span = document.createElement('span');
span.innerText = file.name;
clipsList.appendChild(span);
};
reader.readAsText(file);
}
});
downloadButton.addEventListener('click', () => viewedRecord != null && unsafeWindow.open(
URL.createObjectURL(new Blob([serializeArray(savedRecords[viewedRecord].data)], { type: 'text/plain' }))));
unsafeWindow.addEventListener('keyup', event => {
event.stopPropagation();
event.preventDefault();
if (event.key == settings.toggleKey && event.ctrlKey) {
if (currentRecord) {
endCurrentRecord();
} else {
const d = new Date();
savedRecords.push(currentRecord = {
name: `Clip ${d.getFullYear()}-${d.getMonth()}-${d.getDate()}_${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`,
data: []
/* , game: 'agma.io' */
});
settings.autoStopAfter && setTimeout(endCurrentRecord, settings.autoStopAfter * 1000);
start = performance.now();
recordButton.firstElementChild.style.color = 'red';
}
} else if (event.key == settings.toggleMouseLock) {
isMouseLocked = !isMouseLocked;
unsafeWindow[isMouseLocked ? 'onkeydown' : 'onkeyup']({ keyCode: toggleMouseCode });
}
});
function endCurrentRecord() {
if (!currentRecord) return;
recordButton.firstElementChild.style.color = '';
const span = document.createElement('span');
span.innerText = currentRecord.name;
clipsList.appendChild(span);
currentRecord = null;
}
function updateButtonsStyle() {
if (isPaused) {
recordButton.firstElementChild.style.color = '';
pauseButton.style.display = 'none';
playButton.style.display = 'block';
} else {
recordButton.firstElementChild.style.color = 'green';
playButton.style.display = 'none';
pauseButton.style.display = 'block';
}
}
function recordPacket(message) {
const data = new DataView(message.data);
const id = data.getUint8(0);
if (
id == 11 && data.byteLength == 3
|| id == 12 && data.byteLength == 5
)
return;
if (viewedRecord != null) {
if (id == 10) return isPaused && wsOnmessage(FAKE_CELL_UPDATE);
else if (id == 11 || id == 12) return;
}
wsOnmessage(message);
if (
currentRecord
&& viewedRecord == null
&& (settings.recordAnimations || id != 33)
&& (settings.recordLeaderboard || id < 48 && id > 50)
&& (settings.recordMovingBorders || id != 65 && id != 66)
&& MANDATORY_PACKETS.includes(id)
)
currentRecord.data.push({ id, data: message.data, offset: message.timeStamp - start });
}
function goto() {
if (viewedRecord == null) return;
if (!frameIndex)
wsOnmessage(CLEAR_ALL);
const recordData = savedRecords[viewedRecord].data;
const current = recordData[frameIndex++];
timeText.innerText = Math.round((timeControlInput.max - (timeControlInput.value = current.offset)) / 1000) + 's';
MANDATORY_PACKETS.includes(current.id) && wsOnmessage(current); // prevents XSS -> forbids unallowed packets
if (isPaused)
return;
if (recordData[frameIndex])
setTimeout(goto, recordData[frameIndex].offset - current.offset);
else setTimeout(() => {
frameIndex = 0;
wsOnmessage(CLEAR_ALL);
isPaused = true;
updateButtonsStyle();
}, 10);
}
const originalDefineProperty = unsafeWindow.Object.defineProperty;
unsafeWindow.Object.defineProperty = function(obj, prop, descriptor) {
if (obj instanceof WebSocket && obj.url.includes('.agma.io')) {
obj.addEventListener('message', recordPacket);
originalDefineProperty(obj, 'onmessage', {
set: function(onmessage) { wsOnmessage = onmessage; },
get: function() { return wsOnmessage; }
});
}
return originalDefineProperty(obj, prop, descriptor);
}
originalDefineProperty(unsafeWindow, 'onblur', { set: () => 0 }); // prevents mouse lock to be reset
const originalSend = unsafeWindow.WebSocket.prototype.send;
unsafeWindow.WebSocket.prototype.send = function() {
if (viewedRecord != null) return;
return originalSend.apply(this, arguments);
}
let loaded = false;
unsafeWindow.addEventListener('load', () => {
if (loaded || typeof swal == 'undefined') return;
loaded = true;
if (settings.fetchChangeLog) {
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/repos/Grosse-pasteque/io-record/contents/CHANGELOG', true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer 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');
xhr.setRequestHeader('X-GitHub-Api-Version', '2022-11-28');
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const changelog = atob(JSON.parse(xhr.responseText).content);
if (changelog[0] != GM_getValue('changelog', '0')) {
GM_setValue('changelog', changelog[0])
swal({ title: '', text: changelog.slice(1), html: true })
}
} else {
console.error('Error:', xhr.status, xhr.statusText);
}
}
};
xhr.send();
} catch (e) {
console.error("IO-Record, couldn't fetch CHANGELOG:", e);
}
}
recordButton = document.createElement('div');
recordButton.id = 'recordButton';
recordButton.className = 'btn';
recordButton.style = 'right: 220px;';
recordButton.title = `Press <${settings.toggleKey.toUpperCase()}> to record.`;
recordButton.innerHTML = '<i class="fa fa-video-camera fa-lg" style=" margin-top: 8px;"></i>';
recordButton.onclick = function() {
if (clipPlayer && !clipPlayer.closed)
return clipPlayer.focus();
clipPlayer = unsafeWindow.open('', 'subWindow', 'width=300,height=400');
if (!clipPlayer)
return;
clipPlayer.document.open();
clipPlayer.document.appendChild(clipPlayer.document.adoptNode(menuDom));
clipPlayer.document.close();
clipPlayer.addEventListener('unload', () => {
viewedRecord = null;
isPaused = true;
updateButtonsStyle();
menuDom = clipPlayer.document.documentElement;
clipPlayer = null;
});
clipPlayer.focus();
}
document.body.appendChild(recordButton);
const style = document.createElement('style');
style.innerHTML = `
#recordButton {
transition: color .2s;
position: absolute;
top: 10px;
background: #373737;
padding: 7px;
z-index: 135;
opacity: .7;
}
#recordButton:hover {
color: #eee;
opacity: 1;
}
`;
document.head.appendChild(style);
const contextSpectate = document.getElementById("contextSpectate")
const contextHidePlayer = document.createElement('li');
contextHidePlayer.id = 'contextHidePlayer';
contextHidePlayer.className = 'contextmenu-item enabled';
contextHidePlayer.style = 'display: none;';
contextHidePlayer.innerHTML = '<div class="fa fa-eye-slash fa-2x context-icon"></div><p>Hide player</p>';
contextSpectate.parentElement.insertBefore(contextHidePlayer, contextSpectate);
const originalCss = $.prototype.css;
$.prototype.css = function() {
if (this.selector == '#btnFriends')
recordButton.style.right = parseInt(arguments[1]) + 46 + 'px';
return originalCss.apply(this, arguments)
};
setInterval(() => document.body.onmousemove({}), 250000); // anti afk to prevent FIXIT N°2
});
unsafeWindow.onbeforeunload = () => {
clipPlayer?.close();
// GM_setValue('settings', settings);
savedRecords.forEach(record => record.data = serializeArray(record.data))
GM_setValue('records', savedRecords);
};
console.log(`%cIo %cRecord %c- ${GM_info?.script?.version} Loaded`,
'font-weight: bold; font-size: 14pt; color: red;',
'font-weight: bold; font-size: 14pt; color: white;',
'font-weight: bold; font-size: 14pt; color: black;'
);
})();