RoSniperX

frontend launcher hijack mobile stream sniper

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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";
                }
            );
        });
    }
})();