// ==UserScript== // @name MCJS 性能助推器 · MC 全版本 // @namespace workbuddy.mcjs.booster // @version 1.0.0 // @description 网页版 MC(Eaglercraft 全版本 / MCJS 全站)性能增强:动态分辨率缩放、强制硬件渲染、抗锯齿关闭、FPS 监控、硬件诊断、自动适配帧率 // @author WorkBuddy // @match *://mcjs.link/* // @match *://*.mcjs.link/* // @match *://mc.js.cool/* // @match *://*.mc.js.cool/* // @match *://localhost/* // @match *://127.0.0.1/* // @run-at document-start // @grant none // @noframes // @license MIT // ==/UserScript== (function () { 'use strict'; if (window.__MCJS_BOOSTER__) return; window.__MCJS_BOOSTER__ = true; var VERSION = '1.0.0'; var LOG = function () {}; /* ============================================================ * 1. 配置与持久化 * ============================================================ */ var CFG_KEY = 'mcjs_booster_cfg_v1'; var DEFAULTS = { scale: 0.70, // 渲染缩放 0.3 ~ 1.0 auto: true, // 自动适配帧率 target: 55, // 目标帧率 aa: false, // 抗锯齿(需刷新才生效) hud: true, // 显示面板 open: true, collapsed: false, x: 14, y: 14 }; var cfg = {}; try { var raw = localStorage.getItem(CFG_KEY); cfg = Object.assign({}, DEFAULTS, raw ? JSON.parse(raw) : {}); } catch (e) { cfg = Object.assign({}, DEFAULTS); } function saveCfg() { try { localStorage.setItem(CFG_KEY, JSON.stringify(cfg)); } catch (e) {} } /* ============================================================ * 2. WebGL 层拦截:注入参数 + 分辨率缩放 * ============================================================ */ var CANVAS_PROTO = HTMLCanvasElement.prototype; var W_DESC = Object.getOwnPropertyDescriptor(CANVAS_PROTO, 'width'); var H_DESC = Object.getOwnPropertyDescriptor(CANVAS_PROTO, 'height'); var controllers = []; var mainCtl = null; var diag = { type: '未检测到', renderer: '未知', vendor: '未知', maxTex: 0, software: false }; /** * 解析显卡信息,识别软件渲染(SwiftShader / llvmpipe / Basic Render) */ function inspectGL(gl, type) { try { diag.type = (type === 'webgl2') ? 'WebGL 2' : 'WebGL 1'; var ext = gl.getExtension('WEBGL_debug_renderer_info'); if (ext) { diag.renderer = String(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || ''); diag.vendor = String(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) || ''); } else { diag.renderer = String(gl.getParameter(gl.RENDERER) || ''); diag.vendor = String(gl.getParameter(gl.VENDOR) || ''); } diag.maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE) || 0; diag.software = /swiftshader|software|llvmpipe|basic render|microsoft basic/i.test(diag.renderer); } catch (e) {} LOG('GL', diag); } /** * 单个 WebGL 画布的分辨率控制器 */ function ResCtl(canvas, gl, type) { this.canvas = canvas; this.gl = gl; this.s = 1; this.installed = false; this.lw = 1; this.lh = 1; this._lv = 1; this._lh2 = 1; } ResCtl.prototype.install = function () { if (this.installed) return; var c = this.canvas; var gl = this.gl; var self = this; // 记录当前真实缓冲尺寸作为"逻辑尺寸" this.lw = W_DESC.get.call(c) || c.clientWidth || 300; this.lh = H_DESC.get.call(c) || c.clientHeight || 150; // 影子属性:应用读写 canvas.width 时看到的是逻辑尺寸 try { Object.defineProperty(c, 'width', { configurable: true, enumerable: true, get: function () { return self.lw; }, set: function (v) { var n = v | 0; if (n < 1) n = 1; self.lw = n; // 强制写回,保留 canvas.width = canvas.width 清屏语义 self.applyBacking(true); } }); Object.defineProperty(c, 'height', { configurable: true, enumerable: true, get: function () { return self.lh; }, set: function (v) { var n = v | 0; if (n < 1) n = 1; self.lh = n; self.applyBacking(true); } }); } catch (e) { LOG('shadow fail', e); return; } // 视口与裁剪区跟随缩放 try { this._origViewport = gl.viewport; gl.viewport = function (x, y, w, h) { var s = self.s; return self._origViewport.call(gl, x * s, y * s, w * s, h * s); }; this._origScissor = gl.scissor; gl.scissor = function (x, y, w, h) { var s = self.s; return self._origScissor.call(gl, x * s, y * s, w * s, h * s); }; } catch (e) { LOG('viewport hook fail', e); } // 应用读取 drawingBufferWidth 时也应拿到逻辑尺寸,保证内部数学一致 try { Object.defineProperty(gl, 'drawingBufferWidth', { configurable: true, get: function () { return self.lw; } }); Object.defineProperty(gl, 'drawingBufferHeight', { configurable: true, get: function () { return self.lh; } }); } catch (e) {} this.installed = true; }; ResCtl.prototype.applyBacking = function (force) { var c = this.canvas; var s = this.s; var bw = Math.max(1, Math.round(this.lw * s)); var bh = Math.max(1, Math.round(this.lh * s)); if (force || W_DESC.get.call(c) !== bw) W_DESC.set.call(c, bw); if (force || H_DESC.get.call(c) !== bh) H_DESC.set.call(c, bh); if (s < 0.995) { // 缩小缓冲后由浏览器放大回到原显示尺寸 var dpr = window.devicePixelRatio || 1; c.style.width = (this.lw / dpr) + 'px'; c.style.height = (this.lh / dpr) + 'px'; } else { // 恢复 100% 时还原本来的 CSS 尺寸控制 c.style.width = ''; c.style.height = ''; } }; ResCtl.prototype.setScale = function (s) { if (s < 0.30) s = 0.30; if (s > 1) s = 1; if (Math.abs(s - this.s) < 0.002 && this.installed) return; this.s = s; this.install(); this.applyBacking(); }; ResCtl.prototype.restore = function () { this.s = 1; if (this.installed) { this.applyBacking(); this.canvas.style.width = ''; this.canvas.style.height = ''; } }; /** * 拦截 getContext,让所有 WebGL 上下文都强制走硬件 + 关抗锯齿 */ var origGetContext = CANVAS_PROTO.getContext; CANVAS_PROTO.getContext = function (type, attrs) { if (type === 'webgl' || type === 'experimental-webgl' || type === 'webgl2') { var a = {}; if (attrs) { for (var k in attrs) { if (Object.prototype.hasOwnProperty.call(attrs, k)) a[k] = attrs[k]; } } if (cfg.aa !== true) a.antialias = false; a.powerPreference = 'high-performance'; a.desynchronized = true; a.preserveDrawingBuffer = false; var ctx; try { ctx = origGetContext.call(this, type, a); } catch (e) { ctx = origGetContext.call(this, type, attrs); } if (ctx) { for (var i = 0; i < controllers.length; i++) { if (controllers[i].canvas === this) return ctx; } controllers.push(new ResCtl(this, ctx, type)); inspectGL(ctx, type); setTimeout(pickMain, 0); setTimeout(pickMain, 800); } return ctx; } return origGetContext.call(this, type, attrs); }; /** * 选出面积最大的 WebGL 画布作为游戏主画布,其余恢复原状 */ function pickMain() { var best = null, bestArea = 0; for (var i = 0; i < controllers.length; i++) { var c = controllers[i].canvas; var w = c.clientWidth || c.width || 0; var h = c.clientHeight || c.height || 0; var area = w * h; if (area > bestArea) { bestArea = area; best = controllers[i]; } } if (!best) return; mainCtl = best; for (var j = 0; j < controllers.length; j++) { var ctl = controllers[j]; if (ctl === best) ctl.setScale(cfg.scale); else if (ctl.s !== 1) ctl.restore(); } } /* ============================================================ * 3. 帧率监控 * ============================================================ */ var perf = { frames: 0, last: performance.now(), fps: 0, ms: 0, hist: [], // 每次采样的 fps frameTimes: [], low1: 0 }; function frameTick(now) { perf.frames++; var dt = now - perf.last; perf.frameTimes.push(dt); if (perf.frameTimes.length > 240) perf.frameTimes.shift(); if (dt >= 500) { perf.fps = Math.round((perf.frames * 1000) / dt); perf.ms = +(dt / perf.frames).toFixed(1); perf.frames = 0; perf.last = now; perf.hist.push(perf.fps); if (perf.hist.length > 60) perf.hist.shift(); var sorted = perf.frameTimes.slice().sort(function (a, b) { return a - b; }); var idx = Math.floor(sorted.length * 0.99); perf.low1 = sorted.length ? Math.round(1000 / sorted[Math.min(idx, sorted.length - 1)]) : 0; autoTune(now); if (ui.fps) ui.render(); } requestAnimationFrame(frameTick); } /* ============================================================ * 4. 自动适配 * ============================================================ */ var lastAdjust = 0; var cpuHintShown = false; function autoTune(now) { if (!cfg.auto || !mainCtl) return; if (now - lastAdjust < 1200) return; var f = perf.fps; var s = cfg.scale; if (f < cfg.target - 6 && s > 0.36) { cfg.scale = Math.max(0.30, +(s - 0.06).toFixed(2)); saveCfg(); lastAdjust = now; mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); } else if (f > cfg.target + 10 && s < 0.995) { cfg.scale = Math.min(1, +(s + 0.04).toFixed(2)); saveCfg(); lastAdjust = now; mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); } else if (f < cfg.target - 15 && s <= 0.36 && !cpuHintShown) { cpuHintShown = true; ui.warn('分辨率已到最低仍不达标 → CPU 瓶颈。建议换站内的 WASM 版(体积只有 JS 版一半,执行效率高很多),并调低渲染距离。', 'warn'); } } /* ============================================================ * 5. 界面 * ============================================================ */ var ui = { host: null, fps: null, ms: null, spark: null, info: null, warnEl: null, scaleInput: null, scaleOut: null, autoInput: null, targetSel: null, collapsed: false, // 占位,build 成功后会被真实实现覆盖,避免界面不存在时抛错 render: function () {}, syncControls: function () {}, warn: function () {}, hideWarn: function () {}, place: function () {}, applyVisible: function () {}, applyCollapsed: function () {}, drawSpark: function () {} }; var CSS = [ ':host{all:initial}', '.host{position:fixed;z-index:2147483000;pointer-events:none;font:12px/1.45 -apple-system,"Segoe UI","Microsoft YaHei",sans-serif;color:#d7dde5}', '.panel{pointer-events:auto;width:262px;background:#101318;border:1px solid #2a3038;border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.55);overflow:hidden}', '.hd{display:flex;align-items:center;gap:6px;padding:8px 10px;background:#151a21;border-bottom:1px solid #232a33;cursor:move;user-select:none}', '.hd .dot{width:7px;height:7px;border-radius:50%;background:#4dd4c4;box-shadow:0 0 6px #4dd4c4}', '.hd .t{font-weight:600;font-size:12px;color:#e6edf5;letter-spacing:.3px}', '.hd .v{font-size:10px;color:#5b6672;margin-left:auto}', '.hd button{pointer-events:auto;background:none;border:0;color:#7b8794;font-size:14px;line-height:1;cursor:pointer;padding:0 2px}', '.hd button:hover{color:#e6edf5}', '.body{padding:10px}', '.fpsrow{display:flex;align-items:baseline;gap:5px;margin-bottom:6px}', '.fps{font-size:30px;font-weight:700;line-height:1;color:#4ade80;font-variant-numeric:tabular-nums}', '.unit{font-size:11px;color:#5b6672}', '.ms{margin-left:auto;font-size:11px;color:#7b8794;font-variant-numeric:tabular-nums}', '.spark{display:block;width:100%;height:34px;background:#0b0e12;border-radius:5px;margin-bottom:8px}', '.warn{display:none;font-size:11px;line-height:1.5;padding:6px 8px;border-radius:6px;margin-bottom:8px}', '.warn.show{display:block}', '.warn.warn{background:rgba(251,191,36,.12);border:1px solid rgba(251,191,36,.35);color:#fbbf24}', '.warn.bad{background:rgba(248,113,113,.12);border:1px solid rgba(248,113,113,.35);color:#f87171}', '.row{display:flex;align-items:center;gap:8px;margin:7px 0;font-size:11px;color:#9aa6b3}', '.row>span{flex:0 0 52px}', '.row b{flex:0 0 40px;text-align:right;color:#e6edf5;font-weight:600;font-variant-numeric:tabular-nums}', '.row input[type=range]{flex:1;-webkit-appearance:none;height:3px;border-radius:2px;background:#2a3038;outline:0}', '.row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:#4dd4c4;cursor:pointer}', '.row.chk input{flex:0 0 auto;accent-color:#4dd4c4}', '.row.chk>span{flex:1}', '.row select{flex:1;background:#0b0e12;color:#d7dde5;border:1px solid #2a3038;border-radius:5px;padding:2px 4px;font-size:11px}', '.info{margin-top:8px;padding-top:8px;border-top:1px solid #232a33;font-size:10px;line-height:1.6;color:#6b7784;word-break:break-all;font-family:Consolas,Menlo,monospace}', '.info i{color:#4dd4c4;font-style:normal}', '.btns{display:flex;gap:6px;margin-top:9px}', '.btns button{flex:1;padding:5px 0;font-size:11px;background:#1a2029;color:#c3ccd6;border:1px solid #2a3038;border-radius:6px;cursor:pointer}', '.btns button:hover{background:#222a35;color:#fff}', '.btns button.pri{background:#123b38;border-color:#1d5d57;color:#7ee8da}' ].join(''); ui.build = function () { if (ui.host) return; var host = document.createElement('div'); host.className = 'host'; var root; try { root = host.attachShadow({ mode: 'open' }); } catch (e) { return; } var style = document.createElement('style'); style.textContent = CSS; root.appendChild(style); var panel = document.createElement('div'); panel.className = 'panel'; panel.innerHTML = '
MCJS 助推器' + 'v' + VERSION + '
' + '
' + '
--FPS--
' + '' + '
' + '' + '' + '' + '
' + '
' + '
'; root.appendChild(panel); (document.body || document.documentElement).appendChild(host); ui.host = host; ui.panel = panel; ui.fps = panel.querySelector('.fps'); ui.ms = panel.querySelector('.ms'); ui.spark = panel.querySelector('.spark'); ui.info = panel.querySelector('.info'); ui.warnEl = panel.querySelector('.warn'); ui.scaleInput = panel.querySelector('input[type=range]'); ui.scaleOut = panel.querySelector('.row b'); ui.autoInput = panel.querySelector('input[type=checkbox]'); ui.targetSel = panel.querySelector('select'); ui.body = panel.querySelector('.body'); ui.ctx2d = ui.spark.getContext('2d'); // ---- 事件 ---- ui.scaleInput.addEventListener('input', function () { var v = parseInt(this.value, 10) / 100; cfg.scale = v; cfg.auto = false; saveCfg(); if (mainCtl) mainCtl.setScale(v); ui.syncControls(); ui.render(); }); ui.autoInput.addEventListener('change', function () { cfg.auto = this.checked; saveCfg(); cpuHintShown = false; ui.hideWarn(); ui.syncControls(); }); ui.targetSel.addEventListener('change', function () { cfg.target = parseInt(this.value, 10); saveCfg(); }); panel.querySelector('[data-act=collapse]').addEventListener('click', function () { cfg.collapsed = !cfg.collapsed; saveCfg(); ui.applyCollapsed(); }); panel.querySelector('[data-act=fast]').addEventListener('click', function () { cfg.scale = 0.5; cfg.auto = true; cfg.target = 50; saveCfg(); cpuHintShown = false; ui.hideWarn(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); }); panel.querySelector('[data-act=bal]').addEventListener('click', function () { cfg.scale = 0.75; cfg.auto = true; cfg.target = 55; saveCfg(); cpuHintShown = false; ui.hideWarn(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); }); panel.querySelector('[data-act=reset]').addEventListener('click', function () { cfg.scale = 1; cfg.auto = false; saveCfg(); cpuHintShown = false; ui.hideWarn(); if (mainCtl) mainCtl.setScale(1); ui.syncControls(); ui.render(); }); // ---- 拖动 ---- var hd = panel.querySelector('.hd'); var dragging = false, ox = 0, oy = 0; hd.addEventListener('mousedown', function (e) { if (e.target.tagName === 'BUTTON') return; dragging = true; var r = host.getBoundingClientRect(); ox = e.clientX - r.left; oy = e.clientY - r.top; e.preventDefault(); }); window.addEventListener('mousemove', function (e) { if (!dragging) return; var nx = Math.max(0, Math.min(window.innerWidth - 80, e.clientX - ox)); var ny = Math.max(0, Math.min(window.innerHeight - 40, e.clientY - oy)); cfg.x = nx; cfg.y = ny; ui.place(); }); window.addEventListener('mouseup', function () { if (dragging) { dragging = false; saveCfg(); } }); // ---- 快捷键 ---- window.addEventListener('keydown', function (e) { if (!e.altKey || e.ctrlKey || e.metaKey) return; var tag = (e.target && e.target.tagName || '').toUpperCase(); if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return; var k = e.key.toLowerCase(); if (k === 'm') { cfg.hud = !cfg.hud; saveCfg(); ui.applyVisible(); e.preventDefault(); } else if (k === '1') { cfg.scale = 0.5; cfg.auto = true; saveCfg(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); } else if (k === '2') { cfg.scale = 0.75; cfg.auto = true; saveCfg(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); } else if (k === '3') { cfg.scale = 1; cfg.auto = false; saveCfg(); if (mainCtl) mainCtl.setScale(1); ui.syncControls(); } else if (k === 'arrowup') { cfg.auto = false; cfg.scale = Math.min(1, cfg.scale + 0.05); saveCfg(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); e.preventDefault(); } else if (k === 'arrowdown') { cfg.auto = false; cfg.scale = Math.max(0.3, cfg.scale - 0.05); saveCfg(); if (mainCtl) mainCtl.setScale(cfg.scale); ui.syncControls(); e.preventDefault(); } }, true); ui.place(); ui.applyVisible(); ui.applyCollapsed(); ui.syncControls(); ui.render(); }; ui.place = function () { if (!ui.host) return; ui.host.style.left = (cfg.x || 14) + 'px'; ui.host.style.top = (cfg.y || 14) + 'px'; }; ui.applyVisible = function () { if (!ui.host) return; ui.host.style.display = cfg.hud ? '' : 'none'; }; ui.applyCollapsed = function () { if (!ui.body) return; ui.body.style.display = cfg.collapsed ? 'none' : 'block'; var b = ui.panel.querySelector('[data-act=collapse]'); if (b) b.textContent = cfg.collapsed ? '+' : '−'; }; ui.syncControls = function () { if (!ui.scaleInput) return; ui.scaleInput.value = String(Math.round(cfg.scale * 100)); ui.scaleOut.textContent = Math.round(cfg.scale * 100) + '%'; ui.autoInput.checked = !!cfg.auto; ui.targetSel.value = String(cfg.target); }; ui.warn = function (text, kind) { if (!ui.warnEl) return; ui.warnEl.className = 'warn show ' + (kind || 'warn'); ui.warnEl.textContent = text; }; ui.hideWarn = function () { if (!ui.warnEl) return; ui.warnEl.className = 'warn'; }; ui.render = function () { if (!ui.fps || !cfg.hud) return; var f = perf.fps; ui.fps.textContent = f > 0 ? f : '--'; ui.fps.style.color = f >= cfg.target ? '#4ade80' : (f >= cfg.target * 0.7 ? '#fbbf24' : '#f87171'); var eff = mainCtl ? (mainCtl.lw + '×' + mainCtl.lh) : '--'; var out = mainCtl ? Math.round(mainCtl.lw * cfg.scale) + '×' + Math.round(mainCtl.lh * cfg.scale) : '--'; ui.ms.textContent = perf.ms ? (perf.ms.toFixed(1) + ' ms · 1%low ' + perf.low1) : '--'; var eng = 'MCJS'; try { if (window.eaglercraftXOpts) eng = 'EaglercraftX'; else if (window.eaglercraftOpts) eng = 'Eaglercraft'; } catch (e) {} ui.info.innerHTML = '引擎 ' + eng + ' · 版本 ' + detectVersion() + '
' + '画布 ' + eff + ' → 实际 ' + out + '
' + diag.type + ' · ' + (diag.renderer || '未知') + '
' + 'DPR ' + (window.devicePixelRatio || 1) + ' · 缩放 ' + Math.round(cfg.scale * 100) + '%' + (cfg.auto ? ' · 自动' : ' · 手动'); // 软件渲染警告(优先级最高的提示) if (diag.software) { ui.warn('检测到软件渲染(' + diag.renderer + ')!硬件加速未生效,GPU 在由 CPU 模拟。先解决这个,性能提升最大。', 'bad'); } ui.drawSpark(); }; ui.drawSpark = function () { var ctx = ui.ctx2d; if (!ctx) return; var w = ui.spark.width, h = ui.spark.height; ctx.clearRect(0, 0, w, h); var hist = perf.hist; if (!hist.length) return; var max = Math.max(cfg.target + 10, 30); for (var i = 0; i < hist.length; i++) { var v = hist[i]; var bh = Math.max(1, Math.min(h, Math.round((v / max) * h))); var bw = w / 60; var x = i * bw; ctx.fillStyle = v >= cfg.target ? 'rgba(74,222,128,.85)' : (v >= cfg.target * 0.7 ? 'rgba(251,191,36,.85)' : 'rgba(248,113,113,.85)'); ctx.fillRect(x, h - bh, Math.max(1, bw - 1), bh); } // 目标线 var ty = h - Math.round((cfg.target / max) * h); ctx.fillStyle = 'rgba(77,212,196,.45)'; ctx.fillRect(0, ty, w, 1); }; function detectVersion() { try { var parts = location.pathname.split('/').filter(Boolean); for (var i = 0; i < parts.length; i++) { if (/^\d+(\.\d+)*$/.test(parts[i])) return parts[i]; } var q = new URLSearchParams(location.search).get('v'); if (q) return q; } catch (e) {} return '未知'; } /* ============================================================ * 6. 对外 API(供其它脚本 / 自测页调用) * ============================================================ */ window.__MCJS_BOOSTER_API__ = { version: VERSION, setScale: function (s) { s = Math.min(1, Math.max(0.3, Number(s) || 1)); cfg.auto = false; cfg.scale = s; saveCfg(); if (mainCtl) mainCtl.setScale(s); ui.syncControls(); return s; }, setAuto: function (on) { cfg.auto = !!on; saveCfg(); ui.syncControls(); }, show: function (v) { cfg.hud = v !== false; saveCfg(); ui.applyVisible(); }, getState: function () { return { fps: perf.fps, ms: perf.ms, low1: perf.low1, scale: cfg.scale, target: cfg.target, auto: cfg.auto, canvas: mainCtl ? [mainCtl.lw, mainCtl.lh] : null, buffer: mainCtl ? [ Math.round(mainCtl.lw * mainCtl.s), Math.round(mainCtl.lh * mainCtl.s) ] : null, renderer: diag.renderer, software: diag.software, webgl: diag.type }; } }; /* ============================================================ * 7. 启动 * ============================================================ */ function boot() { ui.build(); if (!ui.host) return; setInterval(pickMain, 700); requestAnimationFrame(frameTick); saveCfg(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot, { once: true }); } else { boot(); } // 画布尺寸变化时重新判定主画布 window.addEventListener('resize', function () { setTimeout(pickMain, 120); }); })();