Taming.io Zoom hack

Allows to change zoom of the game using mouse wheel

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name Taming.io Zoom hack
// @author Murka
// @description Allows to change zoom of the game using mouse wheel
// @icon https://taming.io/img/interface/favicon.png
// @version 0.1
// @match *://taming.io/*
// @run-at document-start
// @grant none
// @license MIT
// @namespace https://greasyfork.org/users/919633
// ==/UserScript==
/* jshint esversion:8 */

/*
    DISCORD: https://discord.gg/cPRFdcZkeD
    GITHUB: https://github.com/murka007

    THIS SCRIPT MAY GET YOUR ACCOUNT BANNED
    TO SAFELY TEST IT, USE A TOR BROWSER!!! 
*/

(function() {
    "use strict";

    // Fix console.log
    const log = console.log;

    class Regex {
        constructor(code, unicode) {
            this.code = code;
            this.COPY_CODE = this.code;
            this._unicode = unicode || false;
            this._ANY_LETTER = "(?:[^\\x00-\\x7F-]|\\$|\\w)";
            this.hooks = {};
            this._NumberSystem = [
                { type: "binary", radix: 2, template: "0b" },
                { type: "octal", radix: 8, template: "0+" },
                { type: "decimal", radix: 10, template: "" },
                { type: "hexadecimal", radix: 16, template: "0x" },
            ];
            this._REPLACE_TYPE = {
                APPEND: 0,
                PREPEND: 1
            };
        }

        static parseData(value) {
            try {
                return Function(`return (${value});`)();
            } catch(err) {
                return null;
            }
        }

        _isRegexp(regex) {
            return Object.prototype.toString.call(regex) === "[object RegExp]";
        }

        // Used to replace number into it's representation in different number systems
        _generateNumberSystem(integer) {
            const copy = [...this._NumberSystem];
            const template = copy.map((system) => system.template + integer.toString(system.radix));
            return `(?:${ template.join("|") })`;
        }

        // Used to replace `{VAR}` into `(?:let|var|const)`
        _parseVariable(regex) {
            return regex.replace(/\{VAR\}/g, "(?:let|var|const)");
        }

        _parseNumber(regex) {
            return regex.replace(/NUMBER\{(.+?)\}/g, (...args) => {
                const number = Number(args[1]);
                return this._generateNumberSystem(number);
            })
        }

        // Used to format regular expressions, can support following types [RegExp, RegExp], [string, string], RegExp, string
        _format(name, regex, flags) {
            if (Array.isArray(regex)) {
                regex = regex.map(exp => this._isRegexp(exp) ? exp.source : exp).join("\\s*");
            }
            if (this._unicode) {
                regex = this._isRegexp(regex) ? regex.source : regex;
                regex = regex.replace(/\\w/g, this._ANY_LETTER);
            }
            regex = this._parseVariable(regex);
            regex = this._parseNumber(regex);
            const expression = new RegExp(regex, flags);
            if (!expression.test(this.code)) throw new Error("Failed to find: " + name);
            return expression;
        }

        _template(type, name, regex, substr) {
            const expression = new RegExp(`(${this._format(name, regex).source})`);
            this.code = this.code.replace(expression, type === this._REPLACE_TYPE.APPEND ? ("$1" + substr) : (substr + "$1"));
        }

        // Used to find some strings, variables etc and use it somewhere else
        match(name, regex, flags, debug) {
            const expression = this._format(name, regex, flags);
            const match = this.code.match(expression);
            this.hooks[name] = {
                expression: expression,
                match: match
            };
            if (debug) log(name, this.hooks[name]);
            return match;
        }

        matchAll(name, regex, flags, debug) {
            const expression = this._format(name, regex, flags);
            const matches = this.code.matchAll(expression);
            this.hooks[name] = {
                expression: expression,
                match: matches
            };
            if (debug) log(name, this.hooks[name]);
            return matches;
        }

        replace(name, regex, substr, flags) {
            const expression = this._format(name, regex, flags);
            this.code = this.code.replace(expression, substr);
        }

        // Used to add something to a string
        append(name, regex, substr) {
            this._template(this._REPLACE_TYPE.APPEND, name, regex, substr);
        }

        // Used to add something before a string
        prepend(name, regex, substr) {
            this._template(this._REPLACE_TYPE.PREPEND, name, regex, substr);
        }
    }

    function defineProperty(target, name, value) {
        Object.defineProperty(target, name, {
            value: value
        })
    }

    function linker(value) {
        const hook = {
            0: value,
            toString: (radix) => hook[0].toString(radix),
            valueOf: () => hook[0].valueOf(),
            get length() {
                return hook[0].length;
            }
        };
        return hook;
    }

    window.max_width = linker(1920 + 192 * 6);
    window.max_height = linker(1080 + 108 * 6);

    function applyRegex(code) {
        // return code;

        const Hook = new Regex(code, true);
        const matches = Hook.matchAll("max_scale", [/const /, /(\w+)/, /=/, /(?=[\d(-+])(.+?);/], "g", true);
        const scale = {
            max_width: false,
            max_height: false
        };

        for (const [match, name, value] of matches) {
            if (scale.max_width && scale.max_height) break;

            const integer = Regex.parseData(value);
            if (Number.isInteger(integer)) {
                if (integer === 1920) {
                    Hook.replace("max_width_var", [`const `, `${name}`, `=.+?;`], `const ${name} = window.max_width;`);
                    scale.max_width = true;
                }
                if (integer === 1080) {
                    Hook.replace("max_height_var", [`const `, `${name}`, `=.+?;`], `const ${name} = window.max_height;`);
                    scale.max_height = true;
                }
            }
        }

        Hook.replace("max_width", [/NUMBER{1920}/, /\//], `window.max_width/`, "gi");
        Hook.replace("max_height", [/NUMBER{1080}/, /\//], `window.max_height/`, "gi");

        return Hook.code;
    }

    async function loadScript(target, script) {

        const response = await fetch(script.src);
        let code = await response.text();
        code = applyRegex(code);

        const blob = new Blob([code], { type: "text/plain" });
        const element = document.createElement("script");
        element.src = URL.createObjectURL(blob);

        // lol
        defineProperty(element, "src", script.src);

        target.appendChild(element);
        URL.revokeObjectURL(element.src);

        START();
    }

    let count = 0;
    const observer = new MutationObserver(function(mutations) {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.tagName === "SCRIPT" && /^https?:\/\/taming\.io\/js\/.+?\.js$/.test(node.src)) {
                    if (count !== 2) {
                        count += 1;
                        return;
                    }
                    observer.disconnect();
                    loadScript(mutation.target, node);

                    // Firefox support
                    function scriptExecuteHandler(event) {
                        event.preventDefault();
                        node.removeEventListener("beforescriptexecute", scriptExecuteHandler);
                    }
                    node.addEventListener("beforescriptexecute", scriptExecuteHandler);
                    node.remove();
                }
            }
        }
    })
    observer.observe(document, { childList: true, subtree: true });

    function START() {

        let resize = null;
        window.addEventListener = new Proxy(window.addEventListener, {
            apply(target, _this, args) {
                const [type, listener, ...options] = args;
                if (type === "resize" && /max_(?:width|height)/.test(listener.toString())) {
                    resize = listener;
                    window.addEventListener = target;
                }
                return target.apply(_this, args);
            }
        })

        let wheels = 0;
        const scaleFactor = {
            width: 192,
            height: 108
        };

        window.addEventListener("wheel", function(event) {
            if (!(event.target instanceof HTMLCanvasElement) || event.ctrlKey) return;
            const { max_width, max_height } = window;

            // Used to create a small gap, so users could easily find the default scale
            if (max_width[0] === 1920 && max_height[0] === 1080) wheels += 1;
            if (wheels % 5 !== 0) return;

            const { width, height } = scaleFactor;
            max_width[0] = Math.max(width, max_width[0] + (event.deltaY > 0 ? -width : width));
            max_height[0] = Math.max(height, max_height[0] + (event.deltaY > 0 ? -height : height));
            if (typeof resize === "function") resize();
        })
    }

})();