Gamee Score Modifier

提交你想要的 Gamee 分数

当前为 2025-03-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Gamee Score Modifier
// @version      1.2.1
// @description  提交你想要的 Gamee 分数
// @author       People11
// @match        https://prizes.gamee.com/*
// @grant        GM_xmlhttpRequest
// @namespace    https://greasyfork.org/users/1143233
// @require      https://update.greasyfork.org/scripts/490306/1345896/Mini%20Md5.js
// ==/UserScript==

(function() {
    'use strict';

    // ============ 全局变量 ============
    let lastGameplayData = null; // 保存最近一次的游戏数据
    let authToken = null; // 认证令牌
    let installUuid = null; // 安装UUID
    let isSubmittingScore = false; // 防止循环请求的标记

    // ============ 添加 CSS 样式 ============
    function addStyles() {
        const css = `
            /* 主样式 */
            #gsm-badge {
                position: fixed; bottom: 10px; right: 10px; padding: 5px 10px;
                background-color: #f44336; color: white; border-radius: 5px;
                font-size: 12px; z-index: 9999; cursor: pointer;
            }
            #gsm-badge.active { background-color: #4CAF50; }

            .gsm-overlay {
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background-color: rgba(0,0,0,0.5); z-index: 9999;
            }

            .gsm-dialog {
                position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                background-color: white; padding: 20px; border-radius: 5px;
                box-shadow: 0 0 10px rgba(0,0,0,0.5); z-index: 10000;
                width: 500px; max-height: 80vh; overflow-y: auto;
            }

            /* 表单元素 */
            .gsm-title { margin-bottom: 15px; }

            .gsm-label {
                display: block; margin-top: 15px; font-weight: bold;
            }

            .gsm-input, .gsm-textarea {
                width: 100%; padding: 8px; box-sizing: border-box; margin-top: 5px;
            }

            .gsm-textarea {
                height: 150px; font-family: monospace;
            }

            .gsm-result {
                margin-top: 15px; padding: 10px; display: none;
                background-color: #f5f5f5; border-radius: 5px; border: 1px solid #ddd;
            }

            /* 按钮样式 */
            .gsm-btn-container {
                display: flex; justify-content: space-between; margin-top: 15px;
            }

            .gsm-btn {
                padding: 8px 16px; color: white; border: none;
                border-radius: 4px; cursor: pointer;
            }

            .gsm-btn-preview {
                display: block; margin-top: 15px; width: 100%; background-color: #2196F3;
            }

            .gsm-btn-submit {
                background-color: #4CAF50; flex: 1; margin-right: 10px;
            }

            .gsm-btn-cancel {
                background-color: #f44336; flex: 1;
            }
        `;

        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    // ============ 工具函数 ============
    // 计算checksum
    function calculateChecksum(score, playTime, gameUrl, gameStateData, uuid) {
        const salt = "crmjbjm3lczhlgnek9uaxz2l9svlfjw14npauhen";
        const str = `${score}:${playTime}:${gameUrl}:${gameStateData || ""}:${uuid}:${salt}`;
        return shenchanran_md5(str);
    }

    // 生成UUID v4
    function generateUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
            const r = Math.random() * 16 | 0;
            const v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    // 获取当前时间的ISO字符串
    function getCurrentTimeISO() {
        // 注意:直接使用toISOString()会使用UTC时间,而不是本地时间带时区
        // 为保持原有逻辑,保留手动格式化方法
        const now = new Date();
        const pad = n => String(n).padStart(2, '0');

        const year = now.getFullYear();
        const month = pad(now.getMonth() + 1);
        const day = pad(now.getDate());
        const hours = pad(now.getHours());
        const minutes = pad(now.getMinutes());
        const seconds = pad(now.getSeconds());

        const tzOffset = now.getTimezoneOffset();
        const tzHours = pad(Math.abs(Math.floor(tzOffset / 60)));
        const tzMinutes = pad(Math.abs(tzOffset % 60));
        const tzSign = tzOffset <= 0 ? '+' : '-';

        return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${tzSign}${tzHours}:${tzMinutes}`;
    }

    // 获取下一个gameplayId
    function getNextGameplayId() {
        let currentMax = localStorage.getItem('gameeScoreModifier_maxGameplayId');
        if (!currentMax && lastGameplayData ?.metadata ?.gameplayId) {
            currentMax = lastGameplayData.metadata.gameplayId;
        }
        currentMax = currentMax ? parseInt(currentMax, 10) : 555;
        const nextId = currentMax + 1;
        localStorage.setItem('gameeScoreModifier_maxGameplayId', nextId.toString());
        return nextId;
    }

    // 从cookie获取认证信息
    function getCookieValue(name) {
        return document.cookie.split(';')
            .map(cookie => cookie.trim())
            .find(cookie => cookie.startsWith(name + '=')) ?.substring(name.length + 1) || null;
    }

    // ============ 捕获游戏数据 ============
    function setupNetworkCapture() {
        // 从cookie中获取认证信息
        try {
            const authCookie = getCookieValue('authentication');
            if (authCookie) authToken = "Bearer " + authCookie;
            installUuid = getCookieValue('uuid');
        } catch (e) {
            console.error('获取认证信息出错:', e);
        }

        // 通用的游戏数据捕获函数
        function captureGameData(body) {
            if (!body || isSubmittingScore) return;

            try {
                const data = JSON.parse(body);
                if (data.method === 'game.saveWebGameplay' && data.params ?.gameplayData) {
                    lastGameplayData = data.params.gameplayData;
                    updateBadge();
                }
            } catch (e) {}
        }

        // 拦截XHR请求
        const origXHROpen = XMLHttpRequest.prototype.open;
        const origXHRSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.open = function(method, url) {
            this._url = url;
            return origXHROpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function(body) {
            if (this._url ?.includes('api.gamee.com')) {
                captureGameData(body);
            }
            return origXHRSend.apply(this, arguments);
        };

        // 拦截fetch请求
        const origFetch = window.fetch;
        window.fetch = function(input, init) {
            if (input ?.includes ?.('api.gamee.com')) {
                captureGameData(init ?.body);
            }
            return origFetch.apply(this, arguments);
        };
    }

    // ============ UI界面 ============
    // 快速创建元素的辅助函数
    function createElement(tag, className, properties = {}) {
        const element = document.createElement(tag);
        if (className) element.className = className;
        Object.assign(element, properties);
        return element;
    }

    // 添加和更新状态徽章
    function addBadge() {
        const existingBadge = document.getElementById('gsm-badge');
        if (existingBadge) existingBadge.remove();

        const badge = createElement('div', '', {
            id: 'gsm-badge',
            textContent: '未捕获游戏数据',
            title: '点击打开分数修改器'
        });

        badge.addEventListener('click', showModifierDialog);
        document.body.appendChild(badge);
        updateBadge();
    }

    function updateBadge() {
        const badge = document.getElementById('gsm-badge');
        if (!badge) return;

        if (lastGameplayData) {
            badge.classList.add('active');
            badge.textContent = `已捕获: ${lastGameplayData.gameId} (分数: ${lastGameplayData.score})`;
        } else {
            badge.classList.remove('active');
            badge.textContent = '未捕获游戏数据';
        }
    }

    // 创建修改分数的弹窗
    function showModifierDialog() {
        if (!lastGameplayData) {
            alert('尚未捕获到游戏数据,请先玩一次游戏并提交分数');
            return;
        }

        // 创建对话框基本结构
        const overlay = createElement('div', 'gsm-overlay');
        const dialog = createElement('div', 'gsm-dialog');

        // 添加标题和游戏信息
        dialog.innerHTML = `
            <h2 class="gsm-title">修改游戏分数</h2>
            <div>
                <p><strong>游戏ID:</strong> ${lastGameplayData.gameId}</p>
                <p><strong>当前分数:</strong> ${lastGameplayData.score}</p>
                <p><strong>当前时长:</strong> ${lastGameplayData.playTime} 秒</p>
            </div>
        `;

        // 添加分数输入
        const scoreLabel = createElement('label', 'gsm-label', { textContent: '目标分数:' });
        const scoreInput = createElement('input', 'gsm-input', {
            type: 'number',
            value: lastGameplayData.score
        });

        dialog.appendChild(scoreLabel);
        dialog.appendChild(scoreInput);

        // 添加结果区域
        const resultInfo = createElement('div', 'gsm-result');
        dialog.appendChild(resultInfo);

        // 添加预览按钮
        const previewBtn = createElement('button', 'gsm-btn gsm-btn-preview', {
            textContent: '预览修改结果'
        });
        dialog.appendChild(previewBtn);

        // 如果有游戏状态数据,添加编辑区
        let gameStateInput = null;
        if (lastGameplayData.gameStateData) {
            const stateLabel = createElement('label', 'gsm-label', { textContent: '其他数据:' });
            gameStateInput = createElement('textarea', 'gsm-textarea');

            try {
                gameStateInput.value = JSON.stringify(JSON.parse(lastGameplayData.gameStateData), null, 2);
            } catch (e) {
                gameStateInput.value = lastGameplayData.gameStateData;
            }

            dialog.appendChild(stateLabel);
            dialog.appendChild(gameStateInput);
        }

        // 添加按钮区域
        const btnContainer = createElement('div', 'gsm-btn-container');
        const submitBtn = createElement('button', 'gsm-btn gsm-btn-submit', {
            textContent: '提交修改后的分数'
        });
        const cancelBtn = createElement('button', 'gsm-btn gsm-btn-cancel', {
            textContent: '取消'
        });

        btnContainer.appendChild(submitBtn);
        btnContainer.appendChild(cancelBtn);
        dialog.appendChild(btnContainer);

        // 预览按钮事件
        previewBtn.addEventListener('click', () => {
            const newScore = parseInt(scoreInput.value, 10);

            if (isNaN(newScore) || newScore <= 0) {
                alert('请输入有效的分数值');
                return;
            }

            const scoreMultiplier = newScore / lastGameplayData.score;
            const newPlayTime = Math.round(lastGameplayData.playTime * scoreMultiplier);
            const newUuid = generateUUID();
            const newChecksum = calculateChecksum(
                newScore,
                newPlayTime,
                lastGameplayData.gameUrl,
                gameStateInput ? gameStateInput.value : lastGameplayData.gameStateData,
                newUuid
            );

            resultInfo.innerHTML = `
                <p><strong>修改后分数:</strong> ${newScore} (${scoreMultiplier.toFixed(2)}倍)</p>
                <p><strong>修改后时长:</strong> ${newPlayTime} 秒</p>
                <p><strong>UUID:</strong> ${newUuid}</p>
                <p><strong>Checksum:</strong> ${newChecksum}</p>
            `;
            resultInfo.style.display = 'block';
        });

        // 取消按钮事件
        cancelBtn.addEventListener('click', () => {
            document.body.removeChild(dialog);
            document.body.removeChild(overlay);
        });

        // 提交按钮事件
        submitBtn.addEventListener('click', () => {
            const newScore = parseInt(scoreInput.value, 10);

            if (isNaN(newScore) || newScore <= 0) {
                alert('请输入有效的分数值');
                return;
            }

            const scoreMultiplier = newScore / lastGameplayData.score;
            const newPlayTime = Math.round(lastGameplayData.playTime * scoreMultiplier);

            // 创建新的游戏数据对象
            const modifiedData = { ...lastGameplayData };
            modifiedData.score = newScore;
            modifiedData.playTime = newPlayTime;

            // 如果有游戏状态数据
            if (gameStateInput) {
                try {
                    JSON.parse(gameStateInput.value); // 验证JSON
                    modifiedData.gameStateData = gameStateInput.value;
                } catch (e) {
                    alert('游戏状态数据不是有效的JSON格式');
                    return;
                }
            }

            // 发送修改后的分数
            sendModifiedScore(modifiedData);

            // 关闭对话框
            document.body.removeChild(dialog);
            document.body.removeChild(overlay);
        });

        // 添加到页面
        document.body.appendChild(overlay);
        document.body.appendChild(dialog);
    }

    // ============ 提交修改后的分数 ============
    function sendModifiedScore(gameplayData) {
        // 生成新标识并创建修改后的数据对象
        const newUuid = generateUUID();
        const newCreatedTime = getCurrentTimeISO();
        const newGameplayId = getNextGameplayId();

        // 使用展开运算符克隆对象并修改
        const modifiedData = {
            ...gameplayData,
            uuid: newUuid,
            createdTime: newCreatedTime,
            metadata: {
                ...(gameplayData.metadata || {}),
                gameplayId: newGameplayId
            }
        };

        // 重新计算checksum
        modifiedData.checksum = calculateChecksum(
            modifiedData.score,
            modifiedData.playTime,
            modifiedData.gameUrl,
            modifiedData.gameStateData,
            newUuid
        );

        // 设置请求标记并准备数据
        isSubmittingScore = true;

        const requestData = {
            jsonrpc: "2.0",
            id: "game.saveWebGameplay",
            method: "game.saveWebGameplay",
            params: { gameplayData: modifiedData }
        };

        // 发送请求
        GM_xmlhttpRequest({
            method: "POST",
            url: "https://api.gamee.com/",
            headers: {
                "Content-Type": "text/plain;charset=UTF-8",
                "Authorization": authToken,
                "X-Install-UUID": installUuid,
                "X-Bot-Header": "gamee",
                "Origin": "https://prizes.gamee.com",
                "Referer": "https://prizes.gamee.com/",
                "User-Agent": navigator.userAgent,
                "X-Score-Modifier": "true"
            },
            data: JSON.stringify(requestData),
            onload: function(response) {
                try {
                    const result = JSON.parse(response.responseText);
                    if (result.result) {
                        alert('分数修改成功!');
                        window.location.reload();
                    } else {
                        alert('分数修改失败:' + JSON.stringify(result.error || '未知错误'));
                    }
                } catch (e) {
                    alert('解析响应失败:' + e.message);
                } finally {
                    isSubmittingScore = false;
                }
            },
            onerror: function(error) {
                alert('请求出错:' + error.message);
                isSubmittingScore = false;
            }
        });
    }

    // ============ 初始化 ============
    // 页面加载完成后初始化
    (document.readyState === 'loading' ?
        document.addEventListener('DOMContentLoaded', init) :
        init)();

    function init() {
        addStyles();
        setupNetworkCapture();
        addBadge();
        console.log('Gamee分数修改器已启动');
    }
})();