Greasy Fork is available in English.

RoSniperX

frontend launcher hijack mobile stream sniper

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         RoSniperX
// @namespace    http://tampermonkey.net/
// @version      6.1
// @description  frontend launcher hijack mobile stream sniper
// @author       Lukas Dobbles (fixed by Grok-Raw)
// @match        https://www.roblox.com/*
// @grant        none
// ==/UserScript==

(function () {
    "use strict";

    const sleep = (ms) => new Promise(r => setTimeout(r, ms));

    const getJSON = async (url, args = {}) => {
        args.headers = args.headers || {};
        args.credentials = "include";

        let retries = 5;

        while (retries--) {
            try {
                const res = await fetch(url, args);

                if (res.status === 429) {
                    await sleep(2000);
                    continue;
                }

                return await res.json();
            } catch (e) {
                if (retries <= 0) throw e;
                await sleep(1500);
            }
        }
    };

    const getUserId = (name) =>
        getJSON("https://users.roblox.com/v1/usernames/users", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                usernames: [name],
                excludeBannedUsers: true
            })
        }).then(d => d?.data?.[0]?.id);

    const getThumb = (id) =>
        getJSON(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${id}&format=Png&size=150x150`)
            .then(d => d.data[0].imageUrl);

    const getServer = (placeId, cursor) => {
        let url = `https://games.roblox.com/v1/games/${placeId}/servers/Public?limit=100`;
        if (cursor) url += "&cursor=" + cursor;
        return getJSON(url);
    };

    const fetchThumbs = (tokens) => {
        const body = tokens.map(token => ({
            requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
            type: "AvatarHeadShot",
            token,
            format: "png",
            size: "150x150"
        }));

        return getJSON("https://thumbnails.roblox.com/v1/batch", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Accept: "application/json"
            },
            body: JSON.stringify(body)
        });
    };

    const search = async (placeId, name, setStatus, cb, setThumb) => {
        try {
            const userId = await getUserId(name);

            if (!userId) {
                setStatus("User not found.");
                cb({ found: false });
                return;
            }

            const thumbUrl = await getThumb(userId);
            setThumb(thumbUrl);
            setStatus("Target avatar loaded...");

            let cursor = null;
            let serverQueue = [];
            let serversScraped = 0;
            let playersScraped = 0;
            const startTime = Date.now();

            do {
                const servers = await getServer(placeId, cursor);

                if (!servers || !servers.data) break;

                for (const place of servers.data) {
                    serversScraped++;

                    if (place.playerTokens && place.playerTokens.length) {
                        playersScraped += place.playerTokens.length;

                        for (const token of place.playerTokens) {
                            serverQueue.push({ token, place });
                        }
                    }
                }

                cursor = servers.nextPageCursor;

                setStatus(`Scanned ${serversScraped} servers / ${playersScraped} players / queued ${serverQueue.length}`);

                await sleep(700);

            } while (cursor);

            setStatus(`Finished full scrape. Comparing ${serverQueue.length} player thumbnails...`);

            let checked = 0;

            while (serverQueue.length > 0) {
                const batch = serverQueue.splice(0, 100);

                try {
                    const thumbs = await fetchThumbs(batch.map(x => x.token));

                    if (!thumbs || !thumbs.data) {
                        await sleep(1000);
                        continue;
                    }

                    checked += batch.length;
                    setStatus(`Compared \( {checked}/ \){playersScraped} thumbnails...`);

                    for (const thumb of thumbs.data) {
                        if (!thumb || !thumb.imageUrl) continue;

                        if (thumb.imageUrl === thumbUrl) {
                            const thumbToken = thumb.requestId.split(":")[1];
                            const matchedPlace = batch.find(x => x.token === thumbToken)?.place;

                            if (matchedPlace) {
                                setStatus(`FOUND TARGET after ${(Date.now() - startTime) / 1000}s`);
                                cb({ found: true, place: matchedPlace });
                                return;
                            }
                        }
                    }

                    await sleep(500);

                } catch {
                    await sleep(1200);
                }
            }

            cb({ found: false });

        } catch (e) {
            setStatus("Search failed: " + e.message);
            cb({ found: false });
        }
    };

    // FIXED & AGGRESSIVE LAUNCH HIJACK
    const hijackLaunchAndJoin = async (targetGameId, setStatus) => {
        try {
            setStatus("Hijacking Roblox frontend launcher...");

            let captured = null;

            const tryCapture = (url) => {
                if (typeof url === "string" && url.includes("robloxmobile://")) {
                    captured = url;
                    return true;
                }
                return false;
            };

            // Backup originals
            const oldOpen = window.open;
            const oldAssign = window.location.assign;
            const oldReplace = window.location.replace;

            // Override window.open
            window.open = function (url, ...args) {
                if (tryCapture(url)) return null;
                return oldOpen.apply(this, [url, ...args]);
            };

            // Aggressive location overrides
            let originalHref = window.location.href;

            try {
                Object.defineProperty(window.location, "href", {
                    configurable: true,
                    enumerable: true,
                    get() { return originalHref; },
                    set(v) {
                        if (tryCapture(v)) return;
                        originalHref = v;
                        oldAssign.call(window.location, v);
                    }
                });
            } catch (e) {}

            window.location.assign = function (url) {
                if (tryCapture(url)) return;
                return oldAssign.call(window.location, url);
            };

            window.location.replace = function (url) {
                if (tryCapture(url)) return;
                return oldReplace.call(window.location, url);
            };

            // Find play button
            const playBtn = 
                document.querySelector('[data-testid="play-button"]') ||
                document.querySelector('.btn-common-play-game-lg') ||
                document.querySelector('.game-play-button') ||
                document.querySelector('#game-detail-play-button') ||
                document.querySelector('button[aria-label*="Play"]');

            if (!playBtn) {
                setStatus("Could not locate Roblox Play button.");
                restore();
                return;
            }

            setStatus("Clicking Play button...");
            playBtn.click();

            // Wait for capture
            let waited = 0;
            while (!captured && waited < 15000) {
                await sleep(400);
                waited += 400;
            }

            restore();

            if (!captured) {
                setStatus("Failed to capture robloxmobile:// URI.");
                return;
            }

            setStatus("Captured launcher. Rewriting target server...");

            let finalUrl = captured;

            if (finalUrl.includes("gameInstanceId=")) {
                finalUrl = finalUrl.replace(/gameInstanceId=[^&]+/, `gameInstanceId=${targetGameId}`);
            } else {
                finalUrl += (finalUrl.includes("?") ? "&" : "?") + `gameInstanceId=${targetGameId}`;
            }

            setStatus("Launching exact target server...");

            // Final launch methods (multiple fallbacks)
            setTimeout(() => {
                window.location.href = finalUrl;
            }, 50);

            setTimeout(() => {
                if (document.hidden) return;
                window.open(finalUrl, "_self");
            }, 300);

        } catch (e) {
            setStatus("Launch hijack failed: " + e.message);
            restore();
        }

        function restore() {
            try {
                window.open = oldOpen;
                window.location.assign = oldAssign;
                window.location.replace = oldReplace;
            } catch {}
        }
    };

    // UI Injection
    const instancesContainer = document.getElementById("running-game-instances-container");

    if (instancesContainer) {
        const containerHeader = document.createElement("div");
        containerHeader.id = "rosniperx";
        containerHeader.style.marginBottom = "15px";

        const headerText = document.createElement("h2");
        headerText.innerText = "RoSniperX v6.1";
        containerHeader.appendChild(headerText);

        const form = document.createElement("form");

        const thumbImage = document.createElement("img");
        thumbImage.height = "50";
        thumbImage.style.display = "none";
        thumbImage.style.borderRadius = "8px";
        thumbImage.style.marginBottom = "8px";
        containerHeader.appendChild(thumbImage);

        const usernameInput = document.createElement("input");
        usernameInput.classList = "input-field";
        usernameInput.placeholder = "Target Username";
        usernameInput.style.width = "100%";
        usernameInput.style.marginBottom = "8px";
        form.appendChild(usernameInput);

        const submitButton = document.createElement("button");
        submitButton.classList = "btn-primary-md";
        submitButton.innerText = "Search & Snipe";
        submitButton.disabled = true;
        form.appendChild(submitButton);

        usernameInput.addEventListener("keyup", e => {
            submitButton.disabled = e.target.value.trim().length === 0;
        });

        const statusText = document.createElement("p");
        statusText.style.margin = "8px 0";
        statusText.style.fontSize = "14px";
        form.appendChild(statusText);

        const joinBtn = document.createElement("button");
        joinBtn.style.display = "none";
        joinBtn.innerText = "🚀 Join Exact Server";
        joinBtn.classList = "btn-control-xs btn-primary-md";
        joinBtn.style.width = "100%";
        joinBtn.style.marginTop = "8px";

        containerHeader.appendChild(form);
        containerHeader.appendChild(joinBtn);

        instancesContainer.insertBefore(containerHeader, instancesContainer.firstChild);

        form.addEventListener("submit", evt => {
            evt.preventDefault();

            joinBtn.style.display = "none";

            const placeId = location.href.match(/games\/(\d+)/)?.[1];

            if (!placeId) {
                statusText.innerText = "Not on a game page.";
                return;
            }

            search(
                placeId,
                usernameInput.value.trim(),
                txt => statusText.innerText = txt,
                result => {
                    if (!result.found) {
                        statusText.innerText = "Target not found after full scan.";
                        return;
                    }

                    joinBtn.style.display = "block";

                    joinBtn.onclick = () => {
                        hijackLaunchAndJoin(result.place.id, txt => statusText.innerText = txt);
                    };
                },
                src => {
                    thumbImage.src = src;
                    thumbImage.style.display = "block";
                }
            );
        });
    }
})();