Io Record

This script is in beta testing !! Record any io game (agma.io only for now)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Io Record
// @namespace    http://tampermonkey.net/
// @version      0.0.10
// @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: on death doesnt show death screen cuz packet 12 is useless and sora is dumb
// FIXIT: make changelog fully text dependable otherwise people scared I'll hack them via XSS
// FIXIT: why shit drop works 1/4 times (on my OS)
// FIXIT: allow moving if spec mode while watching record
// TODO: next frame -> to keydown to allow fastforward
/* TODO: maybe some shits to show
            Interval: 29794 ms
            Packets:       965
            Bytes:      370 Ko

console.log(`Interval: ${Math.round(record[record.length - 1].timeStamp - record[0].timeStamp)}ms\nPackets: ${record.length\nBytes: ${Math.round(record.map(x=>x.data.byteLength).reduce((a, b)=>a+b)/1000)}Ko`);`

*/
// TODO: visual indicator when viewing record ?
/* TODO: optimize files deflate

pako.deflate(new Uint8Array(clip.map(({offset, data}) => new Uint8Array([
    ...new Uint8Array(new Uint32Array([offset]).buffer),
    ...new Uint8Array(data)
])).reduce((a, b) => [...a, ...b], [])))

*/
// TODO: make files into binaries
// FIXIT: depending server some clips may do shit ?
/* FIXIT: I know why cells disapear and it does weird shit
          some packets are missing like 10, 11, 12, 32
          so game doesnt really understand the state its in
*/
// FIXIT: change extension name and clip to binaries [Uint32 "offset", ... "packet"]
// TODO: allow multi file drop -> means add keybind for next/prev clip

(function() {
    'use strict';

    if (unsafeWindow.top !== unsafeWindow.self || document.querySelector('title')?.textContent?.includes('Just a moment'))
        return;

    const settings = Object.assign({
        saveRecordKey: 'o',
        pauseKey: ' ',
        nextFrameKey: 'ArrowRight',
        escapeViewKey: 'Escape',
        recordAnimations: false,
        recordLeaderboard: false,
        recordMovingBorders: true,
        recordFor: 10000,
        fetchChangeLog: true
    }, GM_getValue('settings', {}));

    const SCRIPT_VERSION = GM_info?.script?.version;
    let clientVersion = 'Unknown';

    function versionAlert(clipVersion, errorDetail) {
        swal({
            title: 'Io Record',
            text: `<span style="color:red;font-weight:bold;">An error occured while loading this clip.</span>
            <br>
            <span>Clip Version: ${clipVersion}</span>
            <br>
            <span>Io Record Version: ${SCRIPT_VERSION},${clientVersion}</span>
            <br>
            <span>If your the script isn't up to date you may not be able to view some clips.</span>
            <br>
            <span style="font-size:12pt;">${errorDetail}</span>`,
            html: true,
            type: 'error'
        });
    }
    function versionDiff(a, b) { // a < b
        a = a.split('.');
        b = b.split('.');
        while (a.length && b.length) {
            if (Number(a.shift()) < Number(b.shift()))
                return true;
        }
        return false;
    }
    function serializeArray(frames) {
        return SCRIPT_VERSION + ',' + clientVersion + '\n' + frames.map(frame => Math.round(frame.timeStamp - frames[0].timeStamp) + ': ' + btoa(String.fromCharCode(...new Uint8Array(frame.data)))).join('\n');
    }
    function deserializeString(frames) {
        const [framesep, offsetsep] = frames.includes('\n') ? ['\n', ': '] : ['|', ':'];
        frames = frames.split(framesep);
        const [s, c] = (frames[0].includes(offsetsep) ? '0.0.8,ag255' : frames.shift()).split(',');
        try {            
            return frames.map(frame => {
                const [offset, base64Data] = frame.split(offsetsep);
                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 };
            });
        } catch (error) {
            if (versionDiff(s, SCRIPT_VERSION) || c < clientVersion)
                versionAlert(s + ',' + c, error.name + ': ' + error.message);
            throw error;
        }
    }

    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 };

    const record = [];
    let isPaused = true,
        viewedRecord = null,
        wsOnmessage,
        frameIndex = 0;

    unsafeWindow.addEventListener('keydown', event => viewedRecord && Object.values(settings).includes(event.key) && event.stopImmediatePropagation());
    unsafeWindow.addEventListener('keyup', event => {
        if ($('input, textarea').is(':focus')) return;
        if (viewedRecord) {
            if (event.key == settings.nextFrameKey) {
                if (frameIndex >= viewedRecord.length) return;
                isPaused = true;
                let current;
                do {
                    wsOnmessage(current = viewedRecord[frameIndex++]);
                } while (frameIndex <= viewedRecord.length && current.id != 10);
            } else if (event.key == settings.pauseKey) {
                if (isPaused) {
                    isPaused = false;
                    goto();
                } else {
                    isPaused = true;
                }
            } else if (event.key == settings.escapeViewKey) {
                frameIndex = 0;
                wsOnmessage(CLEAR_ALL);
                viewedRecord = null;
            }
        } else if (event.key == settings.saveRecordKey) {
            const link = document.createElement("a");
            link.href = URL.createObjectURL(new Blob([serializeArray(record)], { type: 'text/plain' }));
            const d = new Date();
            link.download = `agma-clip.bin`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        } else {
            return;
        }
        event.stopImmediatePropagation();
        event.preventDefault();
    });

    function recordPacket(message) {
        const { timeStamp, data } = message;
        const id = new DataView(data).getUint8(0);
        if (
            id == 11 && data.byteLength == 3
            || id == 12 && data.byteLength == 5
        )
            return;
        if (viewedRecord) {
            if (id == 10) return isPaused && wsOnmessage(FAKE_CELL_UPDATE);
            else if (id == 11 || id == 12 || id == 17) return; // prevents spectator movement
        }
        wsOnmessage(message);
        while (timeStamp - record[0]?.timeStamp > settings.recordFor)
            record.shift();
        if (
            viewedRecord
            || !settings.recordAnimations && id == 33
            || !settings.recordLeaderboard && [48, 49, 50].includes(id)
            || !settings.recordMovingBorders && [65, 66].includes(id)
            || !MANDATORY_PACKETS.includes(id)
        )
            return;
        record.push({ timeStamp, data });
    }

    function goto() {
        if (!viewedRecord) return;
        if (!frameIndex)
            wsOnmessage(CLEAR_ALL);
        const current = viewedRecord[frameIndex++];
        MANDATORY_PACKETS.includes(current.id) && wsOnmessage(current);
        if (isPaused)
            return;
        if (viewedRecord[frameIndex])
            setTimeout(goto, viewedRecord[frameIndex].offset - current.offset);
        else setTimeout(() => {
            frameIndex = 0;
            wsOnmessage(CLEAR_ALL);
            isPaused = true;
        }, 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);
    }

    /* Temporarily disabled

    const originalSend = unsafeWindow.WebSocket.prototype.send;
    unsafeWindow.WebSocket.prototype.send = function() {
        // this just prevents mouse position
        if (!arguments[0]?.getUint8(0) && viewedRecord != null) return;
        return originalSend.apply(this, arguments);
    }

    */

    Object.defineProperties(HTMLBodyElement.prototype, { // risky but mandatory
        ondragstart: { get: () => null, set: () => null, configurable: true },
        ondrop: { get: () => null, set: () => null, configurable: true },
        ondragenter: { get: () => null, set: () => null, configurable: true },
        ondragover: { get: () => null, set: () => null, configurable: true }
    });

    let loaded = false;
    unsafeWindow.addEventListener('load', () => {
        if (loaded || typeof swal == 'undefined') return;
        loaded = true;
        for (const { src } of document.scripts) {
            if (clientVersion = src.match("ag[0-9]+")?.[0])
                break;
        }
        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);
            }
        }
        const canvas = document.getElementById('canvas');
        canvas.addEventListener("dragenter", event => {
            event.preventDefault();
            canvas.style.border = '5px solid green';
        });
        canvas.addEventListener("dragover", event => event.preventDefault());
        canvas.addEventListener("dragleave", event => {
            if (event.target != canvas) return;
            event.preventDefault();
            canvas.style.border = '';
        });
        canvas.addEventListener('drop', event => {
            canvas.style.border = '';
            const file = event.dataTransfer.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = e => {
                frameIndex = 0;
                isPaused = false;
                viewedRecord = deserializeString(e.target.result);
                goto();
            };
            reader.readAsText(file);
            event.preventDefault();
        });
        const settingPageId = Math.random() * 10e17;

        const settingTab = document.createElement('button');
        settingTab.id = 'settingTab' + settingPageId;
        settingTab.className = 'setting-tablink';
        settingTab.onclick = openSettingPage.bind(null, settingPageId);
        settingTab.innerText = 'Io Record';

        const settingPage = document.createElement('div');
        settingPage.id = 'settingPage' + settingPageId;
        settingPage.className = 'setting-tabcontent';
        settingPage.innerHTML = `
            <div class="col-md-10 col-md-offset-1 stng" style="padding-left:20px;padding-right:10px;max-height:550px;overflow:hidden auto;margin:0;width:calc(100% - 5px);">
                <span class="hotkey-paragraph"> Keybinds</span>
                <div class="row stng-row">
                    Save Record
                    <div class="hotkey-input" data-name="saveRecordKey">${settings.saveRecordKey}</div>
                    <br>
                    Pause View
                    <div class="hotkey-input" data-name="pauseKey">${settings.pauseKey}</div>
                    <br>
                    Next Frame
                    <div class="hotkey-input" data-name="nextFrameKey">${settings.nextFrameKey}</div>
                    <br>
                    Escape Viewed Record
                    <div class="hotkey-input" data-name="escapeViewKey">${settings.escapeViewKey}</div>
                </div>
                <span class="hotkey-paragraph"> Record</span>
                <div class="row stng-row">
                    Animations
                    <input type="checkbox" data-name="recordAnimations" ${settings.recordAnimations ? "checked" : ''}>
                    <br>
                    Leaderboard
                    <input type="checkbox" data-name="recordLeaderboard" ${settings.recordLeaderboard ? "checked" : ''}>
                    <br>
                    Moving borders
                    <input type="checkbox" data-name="recordMovingBorders" ${settings.recordMovingBorders ? "checked" : ''}>
                </div>
                <span class="hotkey-paragraph"> Other</span>
                <div class="row stng-row">
                    Records Length (seconds)
                    <input type="number" min="0" class="hotkey-input" style="outline:none;border:none;" value="${~~(settings.recordFor / 1000)}">
                    <br>
                    Fetch Change Log
                    <input type="checkbox" data-name="fetchChangeLog" ${settings.fetchChangeLog ? "checked" : ''}>
                </div>
            </div>
        `;

        function onchange() {
            settings[this.dataset.name] = this.checked;
        }
        function onclick() {
            this.classList.add('selected');
            const handle = event => {
                if (event.type === 'keyup')
                    settings[this.dataset.name] = this.innerText = event.key;
                this.classList.remove('selected');
                unsafeWindow.removeEventListener('mousedown', handle);
                unsafeWindow.removeEventListener('keyup', handle);
                event.preventDefault();
                event.stopPropagation()
            };
            unsafeWindow.addEventListener('mousedown', handle);
            unsafeWindow.addEventListener('keyup', handle)
        }
        function oncontextmenu(event) {
            this.innerText = '';
            settings[this.dataset.name] = null;
            event.preventDefault()
        }
        settingPage.querySelectorAll('input[type=checkbox]').forEach(input => input.onchange = onchange.bind(input));
        settingPage.querySelector('input[type=number]').onchange = event => settings.recordFor = 1000 * event.target.value;
        settingPage.querySelectorAll('div.hotkey-input').forEach(hotkey => {
            hotkey.oncontextmenu = oncontextmenu.bind(hotkey);
            hotkey.onclick = onclick.bind(hotkey);
        });

        const style = document.createElement('style');
        style.innerHTML = `
            #settingPage${settingPageId} input {
                position: absolute;
                display: inline-block;
                right: 0;
            }
            #settingPage${settingPageId} .hotkey-input {
                max-width: 90px;
            }
            #settingPage${settingPageId} > div::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }
            #settingPage${settingPageId} > div::-webkit-scrollbar-track {
                background: #282934;
                border-radius: 10px;
            }
            #settingPage${settingPageId} > div::-webkit-scrollbar-thumb {
                background-color: #df8500;
                border-radius: 10px;
                border: 2px solid #282934;
            }
        `;

        const setting = document.getElementById('setting');
        setting.firstElementChild.appendChild(settingTab);
        setting.appendChild(settingPage);
        document.body.appendChild(style);
    });
    unsafeWindow.onbeforeunload = () => void GM_setValue('settings', settings);
    console.log(`🎥 Io Record - ${SCRIPT_VERSION} loaded!`);
})();