A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Features a refined UI, customizable themes, Anti-Lag mode, a music player, and more.
当前为
// ==UserScript==
// @name TriX Executor (BETA) for Territorial.io
// @namespace https://greasyfork.org/en/users/COURTESYCOIL
// @version Beta-Aether-2024.06.12
// @description A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Features a refined UI, customizable themes, Anti-Lag mode, a music player, and more.
// @author Painsel
// @include *
// @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js
// @require https://cdn.jsdelivr.net/npm/@emotion/css@11/dist/emotion-css.umd.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @run-at document-start
// @license MIT
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwYWFmZiI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS5LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy4sixVjVINXY4eiIvPjwvc3ZnPg==
// ==/UserScript==
/* global Prism, unsafeWindow, emotion */
(function() {
'use strict';
const { css, keyframes } = emotion;
// --- Conditional Execution Check ---
const isTerritorialDomain = window.location.hostname.includes('territorial.io');
const urlParams = new URLSearchParams(window.location.search);
let isProxy = false;
if (urlParams.has('__cpo')) {
try {
if (atob(urlParams.get('__cpo')).includes('territorial.io')) {
isProxy = true;
}
} catch (e) { /* Ignore invalid base64 */ }
}
if (!isTerritorialDomain && !isProxy) {
return;
}
// --- Global State & Settings ---
let settings = {
theme: 'abyss', // abyss, terminal_green, crimson_night
antiLag: false,
gameLogToasts: true
};
let interceptionEnabled = false;
let isLoggerSuspended = false;
let queuedMessages = new Map();
let renderInterceptorQueue = () => {};
let customWs = null;
const monitoredConnections = new Map();
let viewingConnection = null;
let updateConnectionStatus = () => {};
let updatePingDisplay = () => {};
let logPacketCallback = () => {};
let showToastCallback = null;
let connectionUrlQueue = [];
let onConnectionStateChange = () => {};
const savedSettings = GM_getValue('trixSettings', {});
settings = { ...settings, ...savedSettings };
// --- UI Theming & Styling (with Emotion) ---
const themes = {
abyss: {
accent: '#0095FF', accentHover: '#33ADFF', accentTranslucent: 'rgba(0, 149, 255, 0.2)',
text: '#E0E0E0', textMuted: '#A0A0A0', textHeader: '#FFFFFF',
bgMain: '#1A1D24', bgHeader: '#242831', bgContent: '#20232A',
bgMainTranslucent: 'rgba(26, 29, 36, 0.85)',
border: '#3A3F4C',
shadow: 'rgba(0, 0, 0, 0.3)',
fontBody: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
fontMono: "'Fira Code', 'Consolas', monospace",
borderRadius: '8px',
specialTextShadow: 'none',
specialContainerFilter: 'none',
},
terminal_green: {
accent: '#00FF9B', accentHover: '#3AFFB2', accentTranslucent: 'rgba(0, 255, 155, 0.2)',
text: '#00FF9B', textMuted: '#00b36d', textHeader: '#3AFFB2',
bgMain: '#0D0D0D', bgHeader: '#121212', bgContent: '#101010',
bgMainTranslucent: 'rgba(13, 13, 13, 0.8)',
border: '#00FF9B',
shadow: '#00b36d',
fontBody: "'Courier New', monospace",
fontMono: "'Courier New', monospace",
borderRadius: '0px',
specialTextShadow: '0 0 5px #00FF9B, 0 0 2px #00FF9B',
specialContainerFilter: 'url(#scanlines)',
},
crimson_night: {
accent: '#E11D48', accentHover: '#F43F5E', accentTranslucent: 'rgba(225, 29, 72, 0.2)',
text: '#FCE7F3', textMuted: '#FDA4AF', textHeader: '#FFFFFF',
bgMain: '#1F0A0E', bgHeader: '#2c0e14', bgContent: '#250c12',
bgMainTranslucent: 'rgba(31, 10, 14, 0.85)',
border: '#881337',
shadow: 'rgba(0, 0, 0, 0.5)',
fontBody: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
fontMono: "'Fira Code', 'Consolas', monospace",
borderRadius: '8px',
specialTextShadow: '1px 1px 2px rgba(0,0,0,0.5)',
specialContainerFilter: 'url(#noise)',
}
};
function createStyles() {
const styleElement = document.createElement('style');
// Define keyframes using Emotion
const slideIn = keyframes`from { transform: translateX(100%) scale(0.98); opacity: 0; } to { transform: translateX(0) scale(1); opacity: 1; }`;
const fadeIn = keyframes`from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); }`;
const toastIn = keyframes`from { transform: translateX(110%); opacity: 0; } to { transform: translateX(0); opacity: 1; }`;
const toastOut = keyframes`from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); }`;
const rainbowGlow = keyframes`0%{box-shadow:0 0 10px 2px hsl(0,100%,60%)} 16%{box-shadow:0 0 10px 2px hsl(60,100%,60%)} 33%{box-shadow:0 0 10px 2px hsl(120,100%,60%)} 50%{box-shadow:0 0 10px 2px hsl(180,100%,60%)} 66%{box-shadow:0 0 10px 2px hsl(240,100%,60%)} 83%{box-shadow:0 0 10px 2px hsl(300,100%,60%)} 100%{box-shadow:0 0 10px 2px hsl(360,100%,60%)}`;
// Generate theme variables
let themeStyles = '';
for (const themeName in themes) {
themeStyles += `
:host([data-theme="${themeName}"]) {
${Object.entries(themes[themeName]).map(([key, value]) => `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${value};`).join('\n')}
}
`;
}
// Use Emotion's `css` helper to generate the main stylesheet
styleElement.textContent = `
/* --- PrismJS Theme (3rd Party) --- */
code[class*="language-"],pre[class*="language-"]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;tab-size:4;}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#2d2d2d}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment{color:#999}.token.punctuation{color:#ccc}.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}
/* --- Base & Theme Variables --- */
:host {
${Object.entries(themes.abyss).map(([key, value]) => `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${value};`).join('\n')}
}
${themeStyles}
/* --- Main UI Elements --- */
#trix-toggle-btn { position: fixed; top: 15px; right: 15px; z-index: 99999; width: 50px; height: 50px; background-color: var(--bg-main-translucent); color: var(--accent); border: 2px solid var(--accent); border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; transition: all 0.3s ease; backdrop-filter: blur(8px); font-family: var(--font-mono); box-shadow: 0 0 15px var(--accent-translucent); }
#trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 20px var(--accent); }
#trix-container { position: fixed; top: 80px; right: 15px; width: 500px; min-height: 450px; z-index: 99998; color: var(--text); font-family: var(--font-body); border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 8px 32px var(--shadow); display: flex; flex-direction: column; backdrop-filter: blur(12px); animation: ${slideIn} 0.5s cubic-bezier(0.25, 1, 0.5, 1); resize: both; background-color: var(--bg-main-translucent); border: 1px solid var(--border); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
#trix-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1; filter: var(--special-container-filter); pointer-events: none; }
#trix-container.hidden { display: none; }
#trix-header, #trix-footer { cursor: move; user-select: none; flex-shrink: 0; }
#trix-header { padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; background-color: var(--bg-header); border-bottom: 1px solid var(--border); }
.trix-title { font-size: 16px; font-weight: 600; color: var(--text-header); text-shadow: var(--special-text-shadow); }
.trix-version-tag { font-size: 10px; font-weight: 300; opacity: 0.7; margin-left: 8px; }
#trix-header-controls { display: flex; align-items: center; gap: 15px; font-size: 12px; }
#trix-close-btn { cursor: pointer; font-size: 20px; font-weight: bold; opacity: 0.7; transition: all 0.2s; }
#trix-close-btn:hover { opacity: 1; color: #ef4444; transform: scale(1.1); }
#trix-conn-status { width: 10px; height: 10px; border-radius: 50%; }
#trix-conn-status.connected { background-color: #28a745; } #trix-conn-status.disconnected { background-color: #dc3545; } #trix-conn-status.connecting { background-color: #ffc107; }
.ping-good { color: #28a745; } .ping-ok { color: #ffc107; } .ping-bad { color: #dc3545; }
/* --- Tabs & Content --- */
#trix-content { padding: 0 15px 15px; flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; background-color: var(--bg-content); }
.trix-tabs { display: flex; flex-wrap: wrap; flex-shrink: 0; border-bottom: 1px solid var(--border); }
.trix-tab { background: transparent; color: var(--text-muted); padding: 10px 15px; cursor: pointer; border-bottom: 2px solid transparent; position: relative; transition: all 0.2s ease; }
.trix-tab:hover { background: var(--bg-header); color: var(--text); }
.trix-tab.active { font-weight: 600; color: var(--text-header); border-bottom-color: var(--accent); background: var(--bg-content); }
.trix-tab-name { padding-right: 15px; } .trix-tab-close { position: absolute; top: 50%; right: 5px; transform: translateY(-50%); opacity: 0.6; } .trix-tab-close:hover { opacity: 1; color: #ef4444; }
#trix-new-tab-btn { background: none; border: none; color: var(--text-muted); font-size: 24px; cursor: pointer; padding: 5px 10px; transition: all 0.2s; }
#trix-new-tab-btn:hover { color: var(--accent); transform: scale(1.1); }
.trix-tab-rename-input { background: transparent; border: 1px solid var(--accent); color: inherit; font-family: inherit; font-size: inherit; padding: 2px; margin: 0; width: 100px; }
.trix-view { display: flex; flex-direction: column; flex-grow: 1; padding-top: 15px; overflow: hidden; }
/* --- Controls & Inputs --- */
.trix-action-bar { display: flex; gap: 10px; margin-top: 10px; flex-shrink: 0; }
.trix-button { background-color: var(--accent); color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 1; transition: all 0.2s; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.2); background-image: linear-gradient(to top, rgba(0,0,0,0.1), transparent); }
.trix-button:hover { filter: brightness(1.1); background-color: var(--accent-hover); transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); }
.trix-button:active { transform: translateY(0); box-shadow: none; }
.trix-button:disabled { background-color: #555; cursor: not-allowed; filter: none; box-shadow: none; }
.trix-input, select.trix-input { background: var(--bg-main); border: 1px solid var(--border); color: var(--text); padding: 8px; border-radius: 5px; font-family: var(--font-mono); box-sizing: border-box; transition: all 0.2s; }
.trix-input:focus, select.trix-input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-translucent); }
.trix-status-bar { margin-top: 10px; padding: 8px; background: rgba(0,0,0,0.2); font-size: 12px; border-radius: 4px; min-height: 1.2em; }
/* --- Specific Views --- */
#trix-welcome-view h2 { margin-top: 0; color: var(--text-header); }
#trix-welcome-view p { color: var(--text); line-height: 1.6; }
#trix-welcome-view code { background: var(--bg-main); padding: 2px 5px; border-radius: 4px; font-family: var(--font-mono); }
#trix-ws-client-view, #trix-packet-log-view, #trix-interceptor-view, #trix-storage-view-container, #trix-script-injector-container { gap: 15px; }
#trix-ws-console, #trix-packet-log, #trix-interceptor-queue { flex-grow: 1; background: var(--bg-main); border: 1px solid var(--border); border-radius: 6px; padding: 10px; overflow-y: auto; font-family: var(--font-mono); font-size: 12px; }
.ws-console-send { color: #7ec699; } .ws-console-receive { color: #cc99cd; } .ws-console-system { color: #f08d49; }
#trix-toggle-interception-btn.active { background-color: #dc3545; box-shadow: 0 0 8px #dc3545; }
/* --- Music Player --- */
#trix-music-toggle-btn { position: fixed; bottom: 20px; left: 20px; z-index: 99999; width: 50px; height: 50px; background-color: var(--bg-main-translucent); color: var(--text); border: 1px solid var(--border); border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(8px); transition: all 0.3s ease; }
#trix-music-toggle-btn:hover { transform: scale(1.1); box-shadow: 0 0 10px var(--accent); color: var(--accent); }
#trix-music-player { display: none; flex-direction: column; position: fixed; bottom: 80px; left: 20px; width: 300px; background: var(--bg-main-translucent); border: 1px solid var(--border); backdrop-filter: blur(12px); border-radius: var(--border-radius); padding: 15px; z-index: 99998; box-shadow: 0 8px 32px var(--shadow); opacity: 0; transform: scale(0.95) translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease; }
#trix-music-player.visible { display: flex; opacity: 1; transform: scale(1) translateY(0); }
#trix-music-player.rainbow-outline { animation: ${rainbowGlow} 2s linear infinite; border-color: transparent; }
.music-title { font-weight: 600; text-shadow: var(--special-text-shadow); }
.music-progress-container { background: var(--bg-content); border-radius: 5px; cursor: pointer; height: 6px; }
.music-progress-bar { background: var(--accent); width: 0%; height: 100%; border-radius: 5px; }
/* --- Modal & Menu --- */
#trix-music-menu, #trix-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); opacity: 0; transition: opacity 0.3s; pointer-events: none; animation: ${fadeIn} 0.3s ease forwards; }
#trix-music-menu.visible, #trix-modal.visible { opacity: 1; pointer-events: auto; }
.music-menu-content, .modal-content { background: var(--bg-content); border: 1px solid var(--border); width: 90%; max-width: 500px; max-height: 80%; border-radius: var(--border-radius); display: flex; flex-direction: column; box-shadow: 0 8px 32px var(--shadow); }
/* --- Toast Notifications --- */
#trix-toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 100001; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
.trix-toast { background-color: var(--bg-header); color: var(--text); border-left: 5px solid var(--accent); padding: 15px; border-radius: var(--border-radius); box-shadow: 0 8px 32px var(--shadow); width: 350px; animation: ${toastIn} 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; overflow: hidden; cursor: pointer; border: 1px solid var(--border); }
.trix-toast.fade-out { animation: ${toastOut} 0.4s ease-in forwards; }
.toast-body code { background: var(--bg-content); padding: 2px 4px; border-radius: 4px; }
.toast-progress { background-color: var(--accent); }
`;
return styleElement;
}
// --- WebSocket Proxy Setup ---
const OriginalWebSocket = unsafeWindow.WebSocket;
unsafeWindow.WebSocket = function(url, protocols) {
let isGameSocket = false;
// Check 1: Direct connection
if (url.includes('/s52/')) {
isGameSocket = true;
} else {
// Check 2: Proxy connection (e.g., __cpw.php?u=BASE64_URL)
try {
const parsedUrl = new URL(url);
const proxyParam = parsedUrl.searchParams.get('u');
if (proxyParam) {
const decodedUrl = atob(proxyParam);
if (decodedUrl.includes('/s52/')) {
isGameSocket = true;
}
}
} catch (e) { /* Not a valid URL or proxy format, ignore */ }
}
if (!isGameSocket) {
return new OriginalWebSocket(url, protocols);
}
console.log(`[TriX] Intercepting WebSocket connection to: ${url}`);
if (showToastCallback) {
showToastCallback(url);
} else {
connectionUrlQueue.push(url);
}
const ws = new OriginalWebSocket(url, protocols);
monitoredConnections.set(ws, { url, state: 'CONNECTING', log: [] });
onConnectionStateChange();
let originalOnMessageHandler = null;
const originalSend = ws.send.bind(ws);
ws.send = function(data) {
logPacketCallback(ws, 'send', data);
if (interceptionEnabled && !settings.antiLag) {
const messageId = `send-${Date.now()}-${Math.random()}`;
const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'send', data, resolve }));
renderInterceptorQueue();
promise.then(decision => {
queuedMessages.delete(messageId);
renderInterceptorQueue();
if (decision.action === 'forward') {
originalSend(decision.data);
logPacketCallback(ws, 'send-forwarded', `[Forwarded] ${decision.data}`);
} else {
logPacketCallback(ws, 'send-blocked', `[Blocked] ${data}`);
}
});
} else {
return originalSend(data);
}
};
Object.defineProperty(ws, 'onmessage', {
get: () => originalOnMessageHandler,
set: (handler) => {
originalOnMessageHandler = handler;
ws.addEventListener('message', event => {
logPacketCallback(ws, 'receive', event.data);
if (interceptionEnabled && !settings.antiLag) {
const messageId = `recv-${Date.now()}-${Math.random()}`;
const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'receive', data: event.data, resolve }));
renderInterceptorQueue();
promise.then(decision => {
queuedMessages.delete(messageId);
renderInterceptorQueue();
if (decision.action === 'forward') {
logPacketCallback(ws, 'receive-forwarded', `[Forwarded] ${decision.data}`);
if (originalOnMessageHandler) originalOnMessageHandler({ data: decision.data });
} else {
logPacketCallback(ws, 'receive-blocked', `[Blocked] ${event.data}`);
}
});
} else {
if (originalOnMessageHandler) originalOnMessageHandler(event);
}
});
},
configurable: true
});
ws.addEventListener('open', () => {
const conn = monitoredConnections.get(ws);
if(conn) conn.state = 'OPEN';
onConnectionStateChange();
updateConnectionStatus('connected', 'Connection established.');
});
ws.addEventListener('close', (event) => {
monitoredConnections.delete(ws);
onConnectionStateChange();
if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) {
unsafeWindow.webSocketManager.activeSocket = null;
updateConnectionStatus('disconnected', `Disconnected. Code: ${event.code}`);
updatePingDisplay('---');
}
});
ws.addEventListener('error', () => {
monitoredConnections.delete(ws);
onConnectionStateChange();
if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) {
updateConnectionStatus('error', 'A connection error occurred.');
}
});
unsafeWindow.webSocketManager = { activeSocket: ws };
return ws;
};
// --- Main script logic ---
function initialize() {
if (settings.antiLag) {
console.log('[TriX] Anti-Lag mode enabled. UI is disabled.');
return;
}
const shadowHost = document.createElement('div');
shadowHost.id = 'trix-host';
shadowHost.dataset.theme = settings.theme;
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
const styleElement = createStyles();
shadowRoot.appendChild(styleElement);
// Add SVG filters for special theme effects
const svgFilters = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgFilters.style.position = 'absolute';
svgFilters.style.width = '0';
svgFilters.style.height = '0';
svgFilters.innerHTML = `
<defs>
<filter id="scanlines">
<feImage href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQYV2NgYGD4z/Cf6WcABgAHiQEtA8s37wAAAABJRU5ErkJggg==" x="0" y="0" width="1" height="2" />
<feTile result="pattern"/>
<feComposite in="pattern" in2="SourceAlpha" operator="in"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0" />
</filter>
<filter id="noise">
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" />
</filter>
</defs>
`;
shadowRoot.appendChild(svgFilters);
let state = {
tabs: [], activeTabId: null,
settings: { position: { top: '80px', right: '15px' }, size: { width: '500px', height: '450px' } },
wsClient: { url: 'wss://echo.websocket.events', protocols: '', autoConnect: false, savedMessages: { 'Example JSON': '{"action":"ping","id":123}'} },
injector: { savedMessages: { 'Example Chat': '42["chat","Hello from TriX!"]'} },
musicPlayer: { volume: 0.5, currentSongIndex: 0, position: { bottom: '80px', left: '20px'} }
};
const $ = (selector, parent = shadowRoot) => parent.querySelector(selector);
const debounce = (func, delay) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => func.apply(this, a), delay); }; };
function loadState() {
const savedState = GM_getValue('trixExecutorState');
if (savedState) {
state.tabs = savedState.tabs || [];
state.activeTabId = savedState.activeTabId;
state.settings = { ...state.settings, ...savedState.settings };
state.wsClient = { ...state.wsClient, ...savedState.wsClient };
state.injector = { ...state.injector, ...savedState.injector };
state.musicPlayer = { ...state.musicPlayer, ...savedState.musicPlayer };
}
const defaultTabs = [
{ id: 'welcome', name: 'Welcome', isPermanent: true },
{ id: 'logger', name: 'Packet Logger', isPermanent: true },
{ id: 'interceptor', name: 'Interceptor', isPermanent: true },
{ id: 'injector', name: 'Injector', isPermanent: true },
{ id: 'ws_client', name: 'WS Client', isPermanent: true },
{ id: 'storage', name: 'Storage', isPermanent: true },
{ id: Date.now(), name: "My First Script", code: "// Welcome to TriX Executor!\nconsole.log('Hello from a user script!');" }
];
['welcome', 'logger', 'interceptor', 'injector', 'ws_client', 'storage'].forEach((id, index) => {
const defaultTab = defaultTabs.find(d => d.id === id);
if (!state.tabs.find(t => t.id === id)) { state.tabs.splice(index, 0, defaultTab); }
});
if (!state.tabs.find(t => t.code)) { state.tabs.push(defaultTabs.find(t => t.code)); }
if (!state.activeTabId || !state.tabs.find(t => t.id === state.activeTabId)) { state.activeTabId = state.tabs[0].id; }
}
const saveState = debounce(() => {
const container = $('#trix-container');
const musicPlayer = $('#trix-music-player');
if (container) {
state.settings.position = { top: container.style.top, left: container.style.left, right: container.style.right, bottom: 'auto' };
state.settings.size = { width: container.style.width, height: container.style.height };
}
if (musicPlayer) {
state.musicPlayer.position = { top: musicPlayer.style.top, left: musicPlayer.style.left, bottom: musicPlayer.style.bottom, right: musicPlayer.style.right };
}
GM_setValue('trixExecutorState', state);
}, 500);
let ui, editor;
function createUI() {
const toggleBtn = document.createElement('div');
toggleBtn.id = 'trix-toggle-btn';
toggleBtn.title = 'Toggle TriX Executor (BETA)';
toggleBtn.innerHTML = 'X';
const container = document.createElement('div');
container.id = 'trix-container';
container.classList.add('hidden');
container.innerHTML = `
<div id="trix-header">
<div><span class="trix-title">TriX Executor</span><span class="trix-version-tag">(v${GM_info.script.version})</span></div>
<div id="trix-header-controls">
<span id="trix-conn-status" class="disconnected" title="No connection"></span>
<span id="trix-ping-display">Ping: ---</span>
<span id="trix-fps-display">FPS: --</span>
<span id="trix-close-btn" title="Close">✖</span>
</div>
</div>
<div id="trix-content"></div>`;
const musicToggle = document.createElement('div');
musicToggle.id = 'trix-music-toggle-btn';
musicToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>`;
const musicPlayer = document.createElement('div');
musicPlayer.id = 'trix-music-player';
musicPlayer.innerHTML = `
<div class="music-drag-handle" style="position: absolute; top: 0; left: 0; width: 100%; height: 20px; cursor: move;"></div>
<div class="music-info" style="text-align: center; margin-bottom: 10px;"><span class="music-title">Select a Song</span><span class="music-artist" style="font-size: 12px; color: var(--text-muted); display: block;">...</span></div>
<div class="music-progress-container"><div class="music-progress-bar"></div></div>
<div class="music-time" style="display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted);"><span id="music-current-time">0:00</span><span id="music-duration">0:00</span></div>
<div class="music-controls" style="display: flex; justify-content: space-around; align-items: center; margin: 10px 0;">
<button class="music-control-btn" id="music-prev-btn" title="Previous"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6 8.5 6V6z"/></svg></button>
<button class="music-control-btn" id="music-rewind-btn" title="Back 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M11.999 5v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z m8 0v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z"/></svg></button>
<button class="music-control-btn music-play-btn" id="music-play-pause-btn"><svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
<button class="music-control-btn" id="music-forward-btn" title="Forward 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m3.999 19 8-6-8-6v12zm8 0 8-6-8-6v12z"/></svg></button>
<button class="music-control-btn" id="music-next-btn" title="Next"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg></button>
</div>
<div class="music-options-bar" style="display: flex; justify-content: space-around; align-items: center; width: 100%; margin-bottom: 10px;">
<button class="music-control-btn" id="music-playlist-btn" title="Playlist"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg></button>
<button class="music-control-btn" id="music-loop-btn" title="Loop"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg></button>
<button class="music-control-btn" id="music-autoplay-btn" title="Autoplay"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m10 16.5 6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></svg></button>
</div>
<div class="music-volume-container" style="display: flex; align-items: center; justify-content: center; position: relative;"><input type="range" min="0" max="1" step="0.01" class="music-volume-slider" id="music-volume-slider"><div id="trix-volume-indicator">100%</div></div>`;
const musicMenu = document.createElement('div');
musicMenu.id = 'trix-music-menu';
musicMenu.innerHTML = `<div class="music-menu-content"><div class="music-menu-header"><input type="text" id="music-search-input" class="trix-input" placeholder="Search songs..."></div><div class="music-song-list"></div></div>`;
const modal = document.createElement('div');
modal.id = 'trix-modal';
modal.innerHTML = `<div class="modal-content"><div class="modal-header"><span>Full Packet Data</span><span id="trix-modal-close" style="cursor:pointer;font-size:20px;">×</span></div><div class="modal-body"><pre id="trix-modal-body-content"></pre></div></div>`;
const toastContainer = document.createElement('div');
toastContainer.id = 'trix-toast-container';
shadowRoot.append(toggleBtn, container, musicToggle, musicPlayer, musicMenu, modal, toastContainer);
return { toggleBtn, container, musicPlayer, modal };
}
// ... (The rest of the JS logic remains largely the same)
// Only functions that directly manipulate HTML or styles need to be aware of the new class names if they changed.
// But since this was a style-only overhaul, most of the logic can stay untouched. The original functions for
// rendering tabs, handling clicks, etc., will work as before.
// Minor adjustments might be needed if class names were changed for styling purposes.
// For example, if .trix-button was changed to .trix-btn, all JS querySelectors would need updating.
// In this refined version, the class names are kept consistent to minimize logical changes.
function applySettings() {
const container = $('#trix-container');
const musicPlayer = $('#trix-music-player');
if (container) { Object.assign(container.style, state.settings.position, state.settings.size); }
if (musicPlayer && state.musicPlayer.position) { Object.assign(musicPlayer.style, state.musicPlayer.position); }
}
updateConnectionStatus = function(status, title) {
const statusIndicator = $('#trix-conn-status');
if (statusIndicator) {
statusIndicator.className = '';
statusIndicator.classList.add(status);
statusIndicator.title = title;
}
};
updatePingDisplay = function(ping) {
const pingDisplay = $('#trix-ping-display');
if (!pingDisplay) return;
pingDisplay.className = '';
if (typeof ping === 'number') {
pingDisplay.textContent = `Ping: ${ping}`;
if (ping < 100) pingDisplay.classList.add('ping-good');
else if (ping < 200) pingDisplay.classList.add('ping-ok');
else pingDisplay.classList.add('ping-bad');
} else {
pingDisplay.textContent = `Ping: ${ping}`;
}
};
async function measurePing() {
const startTime = performance.now();
const url = `${window.location.protocol}//${window.location.host}/favicon.ico?_ts=${Date.now()}`;
try {
const response = await fetch(url, { method: 'HEAD', cache: 'no-store' });
if (response.ok) {
const endTime = performance.now();
updatePingDisplay(Math.round(endTime - startTime));
} else {
updatePingDisplay('Error');
}
} catch (error) {
updatePingDisplay('Error');
}
}
function renderTabs() {
const tabsContainer = $('.trix-tabs');
if (!tabsContainer) return;
tabsContainer.innerHTML = '';
state.tabs.forEach(tab => {
const tabEl = document.createElement('div');
tabEl.className = 'trix-tab';
tabEl.dataset.tabId = tab.id;
if (tab.id === state.activeTabId) tabEl.classList.add('active');
tabEl.innerHTML = `<span class="trix-tab-name">${tab.name}</span>` + (!tab.isPermanent ? `<span class="trix-tab-close">x</span>` : '');
tabsContainer.appendChild(tabEl);
});
const newTabBtn = document.createElement('button');
newTabBtn.id = 'trix-new-tab-btn';
newTabBtn.textContent = '+';
tabsContainer.appendChild(newTabBtn);
renderEditor();
}
function renderActiveView() {
const content = $('#trix-content');
const activeTab = state.tabs.find(t => t.id === state.activeTabId);
content.innerHTML = '';
if (activeTab.id === 'welcome') {
content.innerHTML = `
<div class="trix-view">
<div class="trix-tabs"></div>
<div id="trix-welcome-view" style="padding: 10px;">
<h2>Welcome to TriX Executor!</h2>
<p>This is a powerful tool for web developers and gamers. Here's what you can do:</p>
<ul>
<li><b>Packet Logger:</b> See all network traffic, with tools to copy, view, or suspend the log.</li>
<li><b>Interceptor:</b> Pause, edit, or block WebSocket messages in real-time.</li>
<li><b>Injector:</b> Send custom packets to the live game server.</li>
<li><b>WS Client:</b> Test any WebSocket server by sending custom messages.</li>
<li><b>Storage:</b> View and edit <code>localStorage</code> and <code>sessionStorage</code> directly.</li>
<li><b>Scripts:</b> Create and run your own JavaScript snippets on the page.</li>
</ul>
</div>
</div>`;
renderTabs();
} else if (activeTab.id === 'ws_client') {
content.innerHTML = `
<div id="trix-ws-client-view" class="trix-view">
<div class="trix-tabs"></div>
<div class="trix-ws-client-controls">
<input type="text" id="trix-ws-url" class="trix-input" style="flex-grow:1;" placeholder="WebSocket URL (e.g., wss://...)" value="${state.wsClient.url}">
<button id="trix-ws-connect-btn" class="trix-button" style="flex-grow:0;">Connect</button>
<label><input type="checkbox" id="trix-ws-auto-connect"> Auto-Connect</label>
</div>
<div id="trix-ws-console"><div class="ws-console-system">Status: Disconnected</div></div>
<div class="trix-ws-client-grid">
<textarea id="trix-ws-message" class="trix-input" placeholder="Enter message to send..."></textarea>
<div class="trix-ws-message-controls">
<button id="trix-ws-send-btn" class="trix-button">Send</button>
<select id="trix-ws-saved-messages" class="trix-input"><option value="">Load</option></select>
<button id="trix-ws-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button>
<button id="trix-ws-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button>
</div>
</div>
</div>`;
renderTabs();
renderWsSavedMessages();
const autoConnectCheck = $('#trix-ws-auto-connect');
autoConnectCheck.checked = state.wsClient.autoConnect;
autoConnectCheck.addEventListener('change', () => {
state.wsClient.autoConnect = autoConnectCheck.checked;
saveState();
});
if (state.wsClient.autoConnect && (!customWs || customWs.readyState > 1)) {
connectCustomWs();
}
} else if (activeTab.id === 'interceptor') {
content.innerHTML = `
<div id="trix-interceptor-view" class="trix-view">
<div class="trix-tabs"></div>
<div id="trix-interceptor-controls" class="trix-action-bar">
<button id="trix-toggle-interception-btn" class="trix-button">Enable Interception</button>
<button id="trix-forward-all-btn" class="trix-button" style="background-color:#28a745;">Forward All</button>
<button id="trix-block-all-btn" class="trix-button" style="background-color:#dc3545;">Block All</button>
</div>
<div id="trix-interceptor-queue"></div>
<div class="trix-status-bar">Warning: Enabling interception may cause disconnects if messages are not processed quickly.</div>
</div>`;
renderTabs();
renderInterceptorQueue();
const toggleBtn = $('#trix-toggle-interception-btn');
if (interceptionEnabled) {
toggleBtn.classList.add('active');
toggleBtn.textContent = 'Interception Enabled';
}
} else if (activeTab.id === 'injector') {
content.innerHTML = `
<div id="trix-injector-view" class="trix-view">
<div class="trix-tabs"></div>
<div class="trix-injector-grid" style="flex-grow:1; display:flex; flex-direction:column;">
<textarea id="trix-injector-message" class="trix-input" placeholder="Enter packet data to inject..."></textarea>
<div class="trix-injector-grid">
<button id="trix-inject-btn" class="trix-button">Inject Packet</button>
<div class="trix-injector-message-controls">
<select id="trix-injector-saved-messages" class="trix-input"><option value="">Load</option></select>
<button id="trix-injector-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button>
<button id="trix-injector-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button>
</div>
</div>
</div>
<div class="trix-status-bar">Inject packets into the live game connection.</div>
</div>`;
renderTabs();
renderInjectorSavedMessages();
} else if (activeTab.id === 'logger') {
content.innerHTML = `
<div id="trix-packet-log-view" class="trix-view">
<div class="trix-tabs"></div>
<div id="trix-packet-log-content"></div>
</div>`;
renderTabs();
renderPacketLoggerView();
} else if (activeTab.id === 'storage') {
content.innerHTML = `
<div id="trix-storage-view-container" class="trix-view">
<div class="trix-tabs"></div>
<div id="trix-storage-view"></div>
<div class="trix-action-bar">
<button id="trix-refresh-storage-btn" class="trix-button">Refresh</button>
<button id="trix-add-storage-btn" class="trix-button">Add Local Storage Entry</button>
</div>
</div>`;
renderTabs();
renderStorageView();
} else {
content.innerHTML = `
<div id="trix-script-injector-container" class="trix-view">
<div class="trix-tabs"></div>
<div class="trix-editor-area"></div>
<div class="trix-action-bar">
<button id="trix-execute-btn" class="trix-button">Execute</button>
<button id="trix-clear-btn" class="trix-button">Clear</button>
</div>
<div class="trix-status-bar">Ready.</div>
</div>`;
renderTabs();
}
}
renderInterceptorQueue = function() {
const queueContainer = $('#trix-interceptor-queue');
if (!queueContainer) return;
queueContainer.innerHTML = '';
if (queuedMessages.size === 0) { queueContainer.textContent = 'No messages pending.'; return; }
for (const [id, msg] of queuedMessages.entries()) {
const item = document.createElement('div');
item.className = 'interceptor-item';
item.dataset.messageId = id;
item.innerHTML = `
<div class="interceptor-item-header"><span class="${msg.direction}">${msg.direction.toUpperCase()}</span></div>
<textarea>${msg.data}</textarea>
<div class="interceptor-item-actions">
<button class="trix-button interceptor-btn interceptor-forward-btn">Forward</button>
<button class="trix-button interceptor-btn interceptor-block-btn">Block</button>
</div>`;
queueContainer.appendChild(item);
}
};
function renderStorageView() {
const view = $('#trix-storage-view');
if (!view) return;
view.innerHTML = '';
const storages = { 'Local Storage': unsafeWindow.localStorage, 'Session Storage': unsafeWindow.sessionStorage };
for (const [name, storage] of Object.entries(storages)) {
const container = document.createElement('div');
container.className = 'storage-table';
container.innerHTML = `<div class="storage-header">${name} (${storage.length} items)</div>`;
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i);
const value = storage.getItem(key);
const row = document.createElement('div');
row.className = 'storage-row';
row.dataset.key = key;
row.dataset.storage = name === 'Local Storage' ? 'local' : 'session';
row.innerHTML = `<span class="storage-key" title="${key}">${key}</span><span class="storage-value">${value}</span><span class="storage-delete" title="Delete key">✖</span>`;
container.appendChild(row);
}
view.appendChild(container);
}
}
function renderWsSavedMessages() {
const select = $('#trix-ws-saved-messages');
if (!select) return;
select.innerHTML = '<option value="">Load Message</option>';
for (const name in state.wsClient.savedMessages) {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
}
}
function renderInjectorSavedMessages() {
const select = $('#trix-injector-saved-messages');
if (!select) return;
select.innerHTML = '<option value="">Load Packet</option>';
for (const name in state.injector.savedMessages) {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
}
}
function logToWsClientConsole(message, type = 'system') {
const consoleEl = $('#trix-ws-console');
if (!consoleEl) return;
const line = document.createElement('div');
line.className = `ws-console-${type}`;
line.textContent = `[${type.toUpperCase()}] ${message}`;
consoleEl.appendChild(line);
consoleEl.scrollTop = consoleEl.scrollHeight;
}
function connectCustomWs() {
const urlInput = $('#trix-ws-url');
const connectBtn = $('#trix-ws-connect-btn');
if (customWs && customWs.readyState < 2) { customWs.close(); return; }
state.wsClient.url = urlInput.value.trim();
saveState();
if (!state.wsClient.url) { logToWsClientConsole('Error: URL cannot be empty.', 'system'); return; }
connectBtn.textContent = 'Connecting...';
connectBtn.disabled = true;
logToWsClientConsole(`Connecting to ${state.wsClient.url}...`, 'system');
customWs = new OriginalWebSocket(state.wsClient.url);
customWs.onopen = () => { logToWsClientConsole('Connection established.', 'system'); connectBtn.textContent = 'Disconnect'; connectBtn.classList.add('connected'); connectBtn.disabled = false; };
customWs.onmessage = (event) => { logToWsClientConsole(event.data, 'receive'); };
customWs.onclose = (event) => {
logToWsClientConsole(`Connection closed. Code: ${event.code}`, 'system');
connectBtn.textContent = 'Connect';
connectBtn.classList.remove('connected');
connectBtn.disabled = false;
customWs = null;
if (state.wsClient.autoConnect) {
logToWsClientConsole('Auto-reconnect enabled. Retrying in 3 seconds...', 'system');
setTimeout(connectCustomWs, 3000);
} else {
showToast('WS Client Disconnected', `The connection to <code>${state.wsClient.url}</code> was closed.`, {
action: { text: 'Reconnect', callback: connectCustomWs }
});
}
};
customWs.onerror = () => { logToWsClientConsole('Connection error.', 'system'); connectBtn.textContent = 'Connect'; connectBtn.classList.remove('connected'); connectBtn.disabled = false; customWs = null; };
}
function renderEditor() {
const editorArea = $('.trix-editor-area');
if (!editorArea) { editor = null; return; }
const activeTab = state.tabs.find(t => t.id === state.activeTabId);
if (!activeTab || typeof activeTab.code !== 'string') { editorArea.innerHTML = ''; editor = null; return; }
editorArea.innerHTML = `<textarea spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off"></textarea><pre class="language-js"><code></code></pre>`;
editor = { textarea: $('textarea', editorArea), pre: $('pre', editorArea), code: $('code', editorArea) };
editor.textarea.value = activeTab.code;
highlightCode(activeTab.code);
addEditorEventListeners();
}
function highlightCode(code) { if (editor) editor.code.innerHTML = Prism.highlight(code + '\n', Prism.languages.javascript, 'javascript'); }
function addEditorEventListeners() {
if (!editor) return;
editor.textarea.addEventListener('input', () => {
const activeTab = state.tabs.find(t => t.id === state.activeTabId);
if (activeTab) { activeTab.code = editor.textarea.value; highlightCode(activeTab.code); saveState(); }
});
editor.textarea.addEventListener('scroll', () => { if (editor) { editor.pre.scrollTop = editor.textarea.scrollTop; editor.pre.scrollLeft = editor.textarea.scrollLeft; }});
editor.textarea.addEventListener('keydown', e => {
if (e.key === 'Tab') {
e.preventDefault(); const s = e.target.selectionStart, end = e.target.selectionEnd;
e.target.value = e.target.value.substring(0, s) + ' ' + e.target.value.substring(end);
e.target.selectionStart = e.target.selectionEnd = s + 2;
editor.textarea.dispatchEvent(new Event('input'));
}
});
}
function initEventListeners() {
ui.toggleBtn.addEventListener('click', () => ui.container.classList.toggle('hidden'));
$('#trix-close-btn').addEventListener('click', () => ui.container.classList.add('hidden'));
initDraggable(ui.container, [$('#trix-header'), $('#trix-footer')]);
ui.container.addEventListener('click', handleContainerClick);
ui.container.addEventListener('dblclick', handleContainerDblClick);
const resizeObserver = new ResizeObserver(debounce(saveState, 500));
resizeObserver.observe(ui.container);
ui.modal.addEventListener('click', (e) => {
if (e.target.id === 'trix-modal' || e.target.id === 'trix-modal-close') {
ui.modal.classList.remove('visible');
}
});
onConnectionStateChange = () => {
if (state.activeTabId === 'logger') {
renderPacketLoggerView();
}
};
}
function handleContainerClick(e) {
const target = e.target;
const tabEl = target.closest('.trix-tab');
if (tabEl && !target.classList.contains('trix-tab-close')) {
const tabId = isNaN(parseInt(tabEl.dataset.tabId)) ? tabEl.dataset.tabId : parseInt(tabEl.dataset.tabId);
if (tabId !== state.activeTabId) {
viewingConnection = null;
state.activeTabId = tabId;
renderActiveView();
saveState();
}
}
if (target.classList.contains('trix-tab-close')) {
const tabId = parseInt(tabEl.dataset.tabId, 10);
state.tabs = state.tabs.filter(t => t.id !== tabId);
if (state.activeTabId === tabId) state.activeTabId = state.tabs[0].id;
renderActiveView(); saveState();
}
if (target.id === 'trix-new-tab-btn') {
const newId = Date.now(), newName = `Script ${state.tabs.length}`;
state.tabs.push({ id: newId, name: newName, code: `// ${newName}` });
state.activeTabId = newId;
renderActiveView(); saveState();
}
if (target.id === 'trix-execute-btn') executeScript();
if (target.id === 'trix-clear-btn') {
const activeTab = state.tabs.find(t => t.id === state.activeTabId);
if (activeTab) { activeTab.code = ''; renderEditor(); saveState(); }
}
if (target.id === 'trix-inject-btn') injectPacket();
if (target.classList.contains('storage-delete')) {
const row = target.closest('.storage-row');
const { key, storage } = row.dataset;
if (confirm(`Delete key "${key}"?`)) {
(storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).removeItem(key);
renderStorageView();
}
} else if (target.classList.contains('storage-value')) {
const row = target.closest('.storage-row');
const { key, storage } = row.dataset;
const input = document.createElement('input');
input.type = 'text';
input.className = 'storage-value-input';
input.value = target.textContent;
target.replaceWith(input);
input.focus();
const finishEdit = () => {
(storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).setItem(key, input.value);
renderStorageView();
};
input.addEventListener('blur', finishEdit, { once: true });
input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEdit(); if(e.key === 'Escape') renderStorageView(); });
}
if (target.id === 'trix-toggle-interception-btn') {
interceptionEnabled = !interceptionEnabled;
target.classList.toggle('active', interceptionEnabled);
target.textContent = interceptionEnabled ? 'Interception Enabled' : 'Enable Interception';
}
if (target.id === 'trix-forward-all-btn') {
for (const msg of queuedMessages.values()) { msg.resolve({ action: 'forward', data: msg.data }); }
}
if (target.id === 'trix-block-all-btn') {
for (const msg of queuedMessages.values()) { msg.resolve({ action: 'block' }); }
}
const messageItem = target.closest('.interceptor-item');
if (messageItem) {
const messageId = messageItem.dataset.messageId;
const message = queuedMessages.get(messageId);
if (!message) return;
if (target.classList.contains('interceptor-forward-btn')) {
const editedData = messageItem.querySelector('textarea').value;
message.resolve({ action: 'forward', data: editedData });
} else if (target.classList.contains('interceptor-block-btn')) {
message.resolve({ action: 'block' });
}
}
if (target.id === 'trix-ws-connect-btn') connectCustomWs();
if (target.id === 'trix-ws-send-btn') {
if (customWs && customWs.readyState === 1) {
const message = $('#trix-ws-message').value;
customWs.send(message);
logToWsClientConsole(message, 'send');
} else { logToWsClientConsole('Error: Not connected.', 'system'); }
}
if (target.id === 'trix-ws-save-msg-btn') {
const message = $('#trix-ws-message').value;
if (!message) return;
const name = prompt('Enter a name for this message:', 'My Message');
if (name) { state.wsClient.savedMessages[name] = message; saveState(); renderWsSavedMessages(); }
}
if (target.id === 'trix-ws-del-msg-btn') {
const select = $('#trix-ws-saved-messages');
const name = select.value;
if (name && confirm(`Delete saved message "${name}"?`)) { delete state.wsClient.savedMessages[name]; saveState(); renderWsSavedMessages(); }
}
if (target.id === 'trix-injector-save-msg-btn') {
const message = $('#trix-injector-message').value;
if (!message) return;
const name = prompt('Enter a name for this packet:', 'My Packet');
if (name) { state.injector.savedMessages[name] = message; saveState(); renderInjectorSavedMessages(); }
}
if (target.id === 'trix-injector-del-msg-btn') {
const select = $('#trix-injector-saved-messages');
const name = select.value;
if (name && confirm(`Delete saved packet "${name}"?`)) { delete state.injector.savedMessages[name]; saveState(); renderInjectorSavedMessages(); }
}
if (target.id === 'trix-refresh-storage-btn') renderStorageView();
if (target.id === 'trix-add-storage-btn') {
const key = prompt('Enter new key:');
if (key) { const value = prompt(`Enter value for "${key}":`); unsafeWindow.localStorage.setItem(key, value); renderStorageView(); }
}
if (target.id === 'trix-clear-log-btn' && viewingConnection) {
const conn = monitoredConnections.get(viewingConnection);
if (conn) conn.log = [];
renderPacketLoggerView();
}
if (target.id === 'trix-suspend-log-btn') {
isLoggerSuspended = !isLoggerSuspended;
target.classList.toggle('suspended', isLoggerSuspended);
target.textContent = isLoggerSuspended ? 'Resume Log' : 'Suspend Log';
}
const wsSavedMessagesSelect = target.closest('#trix-ws-saved-messages');
if(wsSavedMessagesSelect && wsSavedMessagesSelect.value) {
$('#trix-ws-message').value = state.wsClient.savedMessages[wsSavedMessagesSelect.value];
}
const injectorSavedMessagesSelect = target.closest('#trix-injector-saved-messages');
if(injectorSavedMessagesSelect && injectorSavedMessagesSelect.value) {
$('#trix-injector-message').value = state.injector.savedMessages[injectorSavedMessagesSelect.value];
}
}
function handleContainerDblClick(e) {
const nameEl = e.target.closest('.trix-tab-name');
if (!nameEl) return;
const tabEl = nameEl.closest('.trix-tab');
const tabId = parseInt(tabEl.dataset.tabId, 10);
const tab = state.tabs.find(t => t.id === tabId);
if (!tab || tab.isPermanent) return;
const input = document.createElement('input');
input.type = 'text';
input.className = 'trix-tab-rename-input';
input.value = tab.name;
nameEl.replaceWith(input);
input.focus();
input.select();
const finishEditing = () => {
const newName = input.value.trim();
if (newName) tab.name = newName;
saveState();
renderTabs();
};
input.addEventListener('blur', finishEditing, { once: true });
input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEditing(); else if (e.key === 'Escape') renderTabs(); });
}
function initDraggable(container, handles) {
let isDragging = false, offsetX, offsetY;
handles.forEach(handle => {
handle.addEventListener('mousedown', e => {
if (e.target.closest('button, input, select, textarea')) return;
isDragging = true;
offsetX = e.clientX - container.offsetLeft;
offsetY = e.clientY - container.offsetTop;
container.style.right = 'auto';
container.style.bottom = 'auto';
document.body.style.userSelect = 'none';
});
});
document.addEventListener('mousemove', e => {
if (isDragging) {
container.style.left = `${e.clientX - offsetX}px`;
container.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
}
});
document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.userSelect = ''; saveState(); } });
}
function injectPacket() {
const statusBar = $('#trix-injector-view .trix-status-bar');
const messageInput = $('#trix-injector-message');
if (!statusBar || !messageInput) return;
const ws = unsafeWindow.webSocketManager?.activeSocket;
if (ws && ws.readyState === 1) {
const message = messageInput.value;
ws.send(message);
statusBar.textContent = `Success: Packet injected at ${new Date().toLocaleTimeString()}`;
} else {
statusBar.textContent = 'Error: Not connected to the game server.';
}
}
function executeScript() {
const statusBar = $('#trix-script-injector-container .trix-status-bar');
if (!statusBar) return;
const activeTab = state.tabs.find(t => t.id === state.activeTabId);
if (!activeTab || !activeTab.code) { statusBar.textContent = 'Nothing to execute.'; return; }
try {
new Function(activeTab.code)();
statusBar.textContent = `Success: Executed '${activeTab.name}' at ${new Date().toLocaleTimeString()}`;
} catch (error) {
console.error('TriX Executor Error:', error);
statusBar.textContent = `Error: ${error.message}`;
}
}
function createPacketElement({ type, data }) {
const item = document.createElement('div');
item.className = `packet-item packet-${type}`;
item.dataset.fullData = data;
const dataSpan = document.createElement('span');
dataSpan.className = 'packet-data';
dataSpan.innerHTML = `<span class="packet-meta">[${type.toUpperCase()}]</span> ${data}`;
const actionsDiv = document.createElement('div');
actionsDiv.className = 'packet-actions';
const copyBtn = document.createElement('button');
copyBtn.className = 'packet-action-btn';
copyBtn.textContent = 'Copy';
copyBtn.onclick = (e) => {
navigator.clipboard.writeText(data).then(() => {
e.target.textContent = 'Copied!';
setTimeout(() => e.target.textContent = 'Copy', 1000);
});
};
actionsDiv.appendChild(copyBtn);
if (type.startsWith('receive')) {
const viewBtn = document.createElement('button');
viewBtn.className = 'packet-action-btn';
viewBtn.textContent = 'View';
viewBtn.onclick = () => {
$('#trix-modal-body-content').textContent = data;
ui.modal.classList.add('visible');
};
actionsDiv.appendChild(viewBtn);
}
item.appendChild(dataSpan);
item.appendChild(actionsDiv);
return item;
}
function renderPacketLoggerView() {
const content = $('#trix-packet-log-content');
if (!content) return;
content.innerHTML = '';
if (viewingConnection) {
const conn = monitoredConnections.get(viewingConnection);
if (!conn) {
viewingConnection = null;
renderPacketLoggerView();
return;
}
const backButton = document.createElement('button');
backButton.className = 'trix-button';
backButton.textContent = '← Back To Monitor';
backButton.style.marginBottom = '10px';
backButton.style.flexGrow = 0;
backButton.onclick = () => { viewingConnection = null; renderPacketLoggerView(); };
const logContainer = document.createElement('div');
logContainer.id = 'trix-packet-log';
logContainer.style.height = 'calc(100% - 90px)';
const fragment = document.createDocumentFragment();
conn.log.forEach(packet => fragment.appendChild(createPacketElement(packet)));
logContainer.appendChild(fragment);
const actionBar = document.createElement('div');
actionBar.className = 'trix-action-bar';
actionBar.innerHTML = `<button id="trix-suspend-log-btn" class="trix-button">Suspend Log</button><button id="trix-clear-log-btn" class="trix-button">Clear Log</button>`;
content.append(backButton, logContainer, actionBar);
logContainer.scrollTop = logContainer.scrollHeight;
const suspendBtn = $('#trix-suspend-log-btn');
if (suspendBtn && isLoggerSuspended) {
suspendBtn.classList.add('suspended');
suspendBtn.textContent = 'Resume Log';
}
} else {
if (monitoredConnections.size === 0) {
content.textContent = 'Waiting for WebSocket connections...';
return;
}
monitoredConnections.forEach((conn, ws) => {
const card = document.createElement('div');
card.className = 'connection-card';
card.style.borderLeftColor = conn.state === 'OPEN' ? '#28a745' : '#ffc107';
card.innerHTML = `<div class="card-header"><span>Active Connection</span><span class="card-state ${conn.state}"><div class="card-state-dot"></div>${conn.state}</span></div><div class="card-url">${conn.url}</div>`;
card.addEventListener('click', () => { viewingConnection = ws; renderPacketLoggerView(); });
content.appendChild(card);
});
}
}
logPacketCallback = function(ws, type, data) {
if (isLoggerSuspended) return;
const conn = monitoredConnections.get(ws);
if (conn) {
conn.log.push({ type, data });
if (viewingConnection === ws) {
const logContainer = $('#trix-packet-log');
if (logContainer) {
const item = createPacketElement({ type, data });
logContainer.appendChild(item);
logContainer.scrollTop = logContainer.scrollHeight;
}
}
}
};
function showToast(title, body, options = {}) {
const toastContainer = $('#trix-toast-container');
if (!toastContainer) return;
const toast = document.createElement('div');
toast.className = 'trix-toast';
const duration = options.duration || (Math.random() * (16000 - 8000) + 8000);
const startTime = performance.now();
let actionsHTML = '';
if (options.action) {
actionsHTML = `<div class="toast-actions"><button class="toast-button">${options.action.text}</button></div>`;
}
toast.innerHTML = `<div class="toast-header"><span>${title}</span><span class="toast-timer">${(duration/1000).toFixed(1)}s</span></div><div class="toast-body">${body}</div>${actionsHTML}<div class="toast-progress"></div>`;
toastContainer.appendChild(toast);
const timerEl = toast.querySelector('.toast-timer');
const progressEl = toast.querySelector('.toast-progress');
const intervalId = setInterval(() => {
const elapsed = performance.now() - startTime;
const remaining = duration - elapsed;
if (remaining <= 0) { clearInterval(intervalId); return; }
timerEl.textContent = `${(remaining / 1000).toFixed(1)}s`;
progressEl.style.width = `${(remaining / duration) * 100}%`;
}, 100);
const dismissToast = () => {
clearInterval(intervalId);
clearTimeout(timeoutId);
toast.classList.add('fade-out');
toast.addEventListener('animationend', () => toast.remove(), { once: true });
};
if (options.action) {
toast.querySelector('.toast-button').addEventListener('click', () => { options.action.callback(); dismissToast(); });
}
const timeoutId = setTimeout(dismissToast, duration);
toast.addEventListener('click', (e) => { if (!e.target.classList.contains('toast-button')) dismissToast(); });
}
loadState();
ui = createUI();
applySettings();
renderActiveView();
initEventListeners();
const musicPlayer = initMusicPlayer();
initDraggable(ui.musicPlayer, [$('.music-drag-handle', ui.musicPlayer)]);
const toastOnConnect = (url) => {
if (settings.antiLag) return;
showToast('New Connection!', `<code>${url}</code>`, {
action: {
text: 'Save to WS Client',
callback: () => {
state.wsClient.url = url;
saveState();
const urlInput = $('#trix-ws-url');
if (urlInput) urlInput.value = url;
showToast('Success', 'URL saved to WS Client.', { duration: 3000 });
}
}
});
};
showToastCallback = toastOnConnect;
connectionUrlQueue.forEach(url => showToastCallback(url));
connectionUrlQueue = [];
setInterval(measurePing, 2000);
let lastFrameTime = performance.now(), frameCount = 0;
const fpsDisplay = $('#trix-fps-display');
function updateFPS(now) {
frameCount++;
if (now >= lastFrameTime + 1000) {
if (fpsDisplay) fpsDisplay.textContent = `FPS: ${frameCount}`;
lastFrameTime = now;
frameCount = 0;
}
requestAnimationFrame(updateFPS);
}
requestAnimationFrame(updateFPS);
let gameLogObserver;
let logMessageQueue = [];
let logToastTimer = null;
function processAndShowLogToast() {
if (logMessageQueue.length === 0) return;
const toastBody = logMessageQueue.map(item => `<div class="toast-log-entry"><span class="toast-log-timestamp">${item.timestamp}</span><span>${item.message}</span></div>`).join('');
showToast('Game Log Update', toastBody, { duration: 8000 });
logMessageQueue = [];
}
function observeGameLog() {
if (gameLogObserver) gameLogObserver.disconnect();
const gameLogContainer = document.querySelector("body > div:nth-child(4) > div:nth-child(1)");
if (gameLogContainer) {
gameLogObserver = new MutationObserver((mutations) => {
if (settings.antiLag || !settings.gameLogToasts) return;
let hasNewNodes = false;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.style.display === 'table') {
const timestampNode = node.querySelector('span:first-child');
const messageNode = node.querySelector('span:last-child');
if (timestampNode && messageNode) {
logMessageQueue.push({ timestamp: timestampNode.textContent, message: messageNode.innerHTML });
hasNewNodes = true;
}
}
});
}
}
if (hasNewNodes) {
clearTimeout(logToastTimer);
logToastTimer = setTimeout(processAndShowLogToast, 1500);
}
});
gameLogObserver.observe(gameLogContainer, { childList: true, subtree: true });
} else {
setTimeout(observeGameLog, 1000);
}
}
observeGameLog();
}
function initMusicPlayer() {
const player = {
audio: document.createElement('audio'),
songList: [
{ title: "Animal I Have Become", artist: "Skillet", url: "https://www.dropbox.com/scl/fi/b3lgo2fkz85h0m3nklx3b/Skilet_-_Animal_I_Have_Become_-mp3.pm-1.mp3?rlkey=yok2i5r5in404ili6ozf776px&st=5xmvo7c5&dl=1" },
{ title: "Passo Bem Solto", artist: "ATLXS", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/ATLXS_-_PASSO_BEM_SOLTO_-_Slowed_@BaseNaija%20(2).mp3" },
{ title: "FROM THE SCREEN TO THE-", artist: "KSI (Audio: Ben4062)", url: "https://www.myinstants.com/media/sounds/from-the-screen-to-the-ring.mp3" },
{ title: "Honeypie", artist: "JAWNY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/JAWNY_-_Honeypie_muzonov.net_(mp3.pm).mp3" },
{ title: "Kamin", artist: "EMIN ft. JONY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/EMIN_Ft_JONY_-_.mp3" },
{ title: "It Has To Be This Way", artist: "Jamie Christopherson", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/Metal%20Gear%20Rising-%20Revengeance%20OST%20-%20It%20Has%20To%20Be%20This%20Way%20_Senator%20Battle_%20-%20Jamie%20Christopherson%20-%20SoundLoadMate.com.mp3" }
],
isPlaying: false, currentSongIndex: 0, volume: 0.5, loop: false, autoplay: true
};
const $ = (sel) => document.querySelector('#trix-host').shadowRoot.querySelector(sel);
const playerEl = $('#trix-music-player');
const titleEl = $('.music-title');
const artistEl = $('.music-artist');
const playPauseBtn = $('#music-play-pause-btn');
const progressBar = $('.music-progress-bar');
const progressContainer = $('.music-progress-container');
const currentTimeEl = $('#music-current-time');
const durationEl = $('#music-duration');
const volumeSlider = $('#music-volume-slider');
const volumeIndicator = $('#trix-volume-indicator');
const loopBtn = $('#music-loop-btn');
const autoplayBtn = $('#music-autoplay-btn');
const menuEl = $('#trix-music-menu');
const songListEl = $('.music-song-list');
let volumeTimeout;
const updateSpecialEffects = () => playerEl.classList.toggle('rainbow-outline', player.isPlaying);
const loadSong = (index) => {
player.currentSongIndex = index;
const song = player.songList[index];
player.audio.src = song.url;
player.audio.loop = player.loop;
titleEl.textContent = song.title;
artistEl.textContent = song.artist;
renderSongList();
updateSpecialEffects();
};
const playSong = () => { player.isPlaying = true; player.audio.play().catch(console.error); playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`; updateSpecialEffects(); };
const pauseSong = () => { player.isPlaying = false; player.audio.pause(); playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`; updateSpecialEffects(); };
const nextSong = () => { let newIndex = player.currentSongIndex + 1; if (newIndex >= player.songList.length) newIndex = 0; loadSong(newIndex); playSong(); };
const prevSong = () => { let newIndex = player.currentSongIndex - 1; if (newIndex < 0) newIndex = player.songList.length - 1; loadSong(newIndex); playSong(); };
const setVolume = (value) => { player.volume = value; player.audio.volume = value; volumeSlider.value = value; volumeIndicator.textContent = `${Math.round(value * 100)}%`; volumeIndicator.classList.add('visible'); clearTimeout(volumeTimeout); volumeTimeout = setTimeout(() => volumeIndicator.classList.remove('visible'), 1000); };
const renderSongList = (filter = '') => {
songListEl.innerHTML = '';
const filteredList = player.songList.filter(s => s.title.toLowerCase().includes(filter) || s.artist.toLowerCase().includes(filter));
filteredList.forEach(song => {
const originalIndex = player.songList.indexOf(song);
const item = document.createElement('div');
item.className = 'music-song-item';
if (originalIndex === player.currentSongIndex) item.classList.add('playing');
item.innerHTML = `<span class="music-song-title">${song.title}</span><span class="music-song-artist">${song.artist}</span>`;
item.addEventListener('click', () => { loadSong(originalIndex); playSong(); menuEl.classList.remove('visible'); });
songListEl.appendChild(item);
});
};
$('#trix-music-toggle-btn').addEventListener('click', () => playerEl.classList.toggle('visible'));
playPauseBtn.addEventListener('click', () => player.isPlaying ? pauseSong() : playSong());
$('#music-next-btn').addEventListener('click', nextSong);
$('#music-prev-btn').addEventListener('click', prevSong);
$('#music-forward-btn').addEventListener('click', () => player.audio.currentTime += 10);
$('#music-rewind-btn').addEventListener('click', () => player.audio.currentTime -= 10);
volumeSlider.addEventListener('input', (e) => setVolume(e.target.value));
loopBtn.addEventListener('click', () => { player.loop = !player.loop; player.audio.loop = player.loop; loopBtn.classList.toggle('active', player.loop); });
autoplayBtn.addEventListener('click', () => { player.autoplay = !player.autoplay; autoplayBtn.classList.toggle('active', player.autoplay); });
player.audio.addEventListener('timeupdate', () => {
const { currentTime, duration } = player.audio;
if (duration) {
progressBar.style.width = `${(currentTime / duration) * 100}%`;
const formatTime = s => `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`;
currentTimeEl.textContent = formatTime(currentTime);
durationEl.textContent = formatTime(duration);
}
});
player.audio.addEventListener('ended', () => { if (player.autoplay) nextSong(); else { pauseSong(); player.audio.currentTime = 0; } });
progressContainer.addEventListener('click', (e) => { if (!isNaN(player.audio.duration)) player.audio.currentTime = (e.offsetX / progressContainer.clientWidth) * player.audio.duration; });
$('#music-playlist-btn').addEventListener('click', () => menuEl.classList.add('visible'));
$('#music-search-input').addEventListener('input', (e) => renderSongList(e.target.value.toLowerCase()));
menuEl.addEventListener('click', (e) => { if(e.target.id === 'trix-music-menu') menuEl.classList.remove('visible'); });
setVolume(player.volume);
autoplayBtn.classList.toggle('active', player.autoplay);
loadSong(player.currentSongIndex);
renderSongList();
return player;
}
function showLoadingScreen() {
const currentTheme = themes[settings.theme] || themes.abyss;
const style = document.createElement('style');
style.textContent = `
#trix-loading-screen, #trix-loading-settings-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #000; z-index: 2147483647; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; font-family: ${currentTheme.fontBody}; opacity: 0; transition: opacity 0.5s ease-in-out; pointer-events: none; }
#trix-loading-screen.visible, #trix-loading-settings-modal.visible { opacity: 1; pointer-events: auto; }
.loading-content { opacity: 1; transition: opacity 1s ease-in-out; text-align: center; }
.loading-content.fade-out { opacity: 0; }
.loading-header { font-size: 5rem; font-weight: bold; color: ${currentTheme.accent}; text-shadow: 2px 2px 0px ${currentTheme.accentHover}, 4px 4px 10px rgba(0,0,0,0.5); margin-bottom: 2rem; }
.loading-bar-container { width: 50%; max-width: 600px; height: 20px; background-color: #2a2a30; border-radius: 10px; border: 1px solid ${currentTheme.border}; overflow: hidden; margin-bottom: 1rem; }
.loading-bar-progress { width: 0%; height: 100%; background: linear-gradient(90deg, ${currentTheme.accent}, ${currentTheme.accentHover}); border-radius: 10px; transition: width 0.1s linear; }
.loading-info { display: flex; justify-content: center; gap: 20px; font-size: 1.2rem; margin-bottom: 2rem; }
.loading-message { font-style: italic; color: #9ca3af; min-height: 1.2em; }
#trix-loading-settings-modal .settings-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem 2rem; align-items: center; margin-top: 2rem; }
#trix-loading-settings-modal .settings-label { text-align: right; font-weight: bold; }
#trix-loading-settings-modal select, #trix-loading-settings-modal input[type="checkbox"] { background: #2a2a30; color: white; border: 1px solid #444; padding: 5px; border-radius: 4px; }
#trix-loading-settings-modal input[type="checkbox"] { width: 20px; height: 20px; }
#trix-loading-settings-modal .trix-button { background-color: ${currentTheme.accent}; color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 0; width: 200px; margin-top: 2rem; transition: filter 0.2s; font-weight: 600; }
#trix-loading-settings-modal .trix-button:hover { filter: brightness(1.1); }
`;
document.documentElement.appendChild(style);
const loadingScreen = document.createElement('div');
loadingScreen.id = 'trix-loading-screen';
const loadingMessages = [ "Disable all other extensions to reduce disconnections (Error 1009).", "the worst it can do is crash your game, right?", "Definitely not a cheating tool...", "did you know? I didnt know", "you can report any harmful scripts to me. (my discord: painsel)", "wanna break from the ads?", "I'm not sure how some features even work, if you're a developer pls help me (my discord: painsel)" ];
loadingScreen.innerHTML = `<div class="loading-content"><div class="loading-header">TriX Executor</div><div class="loading-bar-container"><div class="loading-bar-progress"></div></div><div class="loading-info"><span class="loading-percentage">0.0%</span><span class="loading-timer">...s left</span></div><div class="loading-message">${loadingMessages[0]}</div></div>`;
const settingsModal = document.createElement('div');
settingsModal.id = 'trix-loading-settings-modal';
settingsModal.style.visibility = 'hidden';
settingsModal.innerHTML = `
<div class="loading-content">
<div class="loading-header">Settings</div>
<div class="settings-grid">
<label class="settings-label" for="trix-theme-select">Theme:</label>
<select id="trix-theme-select">
<option value="abyss">Abyss (Default)</option>
<option value="terminal_green">Terminal Green</option>
<option value="crimson_night">Crimson Night</option>
</select>
<label class="settings-label" for="trix-antilag-check">Anti-Lag Mode:</label>
<input type="checkbox" id="trix-antilag-check">
</div>
<button id="trix-resume-loading" class="trix-button">Resume</button>
</div>`;
document.documentElement.append(loadingScreen, settingsModal);
const themeSelect = settingsModal.querySelector('#trix-theme-select');
const antiLagCheck = settingsModal.querySelector('#trix-antilag-check');
themeSelect.value = settings.theme;
antiLagCheck.checked = settings.antiLag;
const saveSettings = () => GM_setValue('trixSettings', settings);
themeSelect.onchange = () => { settings.theme = themeSelect.value; saveSettings(); };
antiLagCheck.onchange = () => { settings.antiLag = antiLagCheck.checked; saveSettings(); };
setTimeout(() => loadingScreen.classList.add('visible'), 10);
const progressBar = loadingScreen.querySelector('.loading-bar-progress');
const percentageText = loadingScreen.querySelector('.loading-percentage');
const timerText = loadingScreen.querySelector('.loading-timer');
const messageText = loadingScreen.querySelector('.loading-message');
const loadingContent = loadingScreen.querySelector('.loading-content');
const randomDuration = Math.random() * (5000 - 2000) + 2000;
let startTime = null;
let lastMessageChange = 0;
let currentMessageIndex = 0;
let isPaused = false;
let pauseTime = 0;
let animFrameId;
function loadingLoop(timestamp) {
if (!startTime) startTime = timestamp;
if (isPaused) { animFrameId = requestAnimationFrame(loadingLoop); return; }
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / randomDuration, 1);
progressBar.style.width = `${progress * 100}%`;
percentageText.textContent = `${(progress * 100).toFixed(1)}%`;
timerText.textContent = `${Math.max(0, (randomDuration - elapsed) / 1000).toFixed(1)}s left`;
if (elapsed > lastMessageChange + 1500 && progress < 0.9) {
lastMessageChange = elapsed;
currentMessageIndex = (currentMessageIndex + 1) % loadingMessages.length;
messageText.textContent = loadingMessages[currentMessageIndex];
}
if (progress < 1) {
animFrameId = requestAnimationFrame(loadingLoop);
} else {
window.removeEventListener('keydown', f12Handler);
percentageText.textContent = '100.0%';
timerText.textContent = '0.0s left';
setTimeout(() => {
loadingContent.classList.add('fade-out');
setTimeout(() => {
loadingScreen.classList.remove('visible');
settingsModal.classList.remove('visible');
setTimeout(() => { loadingScreen.remove(); settingsModal.remove(); style.remove(); }, 1000);
}, 500);
}, 500);
}
}
const f12Handler = (e) => {
if (e.key === 'F12') {
e.preventDefault();
isPaused = true;
pauseTime = performance.now();
settingsModal.style.visibility = 'visible';
settingsModal.classList.add('visible');
}
};
settingsModal.querySelector('#trix-resume-loading').onclick = () => {
settingsModal.classList.remove('visible');
setTimeout(() => settingsModal.style.visibility = 'hidden', 500);
startTime += (performance.now() - pauseTime);
isPaused = false;
};
window.addEventListener('keydown', f12Handler);
animFrameId = requestAnimationFrame(loadingLoop);
}
function run() {
let loadCount = GM_getValue('trixLoadCount', 0);
GM_setValue('trixLoadCount', loadCount + 1);
if ((loadCount + 1) % 5 === 1) {
showLoadingScreen();
}
waitForElement('#canvasA', initialize);
}
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) { callback(); return; }
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
observer.disconnect();
callback();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
})();