Io Record

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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!`);
})();