RoSniperX

frontend launcher hijack mobile stream sniper

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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";
                }
            );
        });
    }
})();