Score spoof, camera spoof, mic spoof, country spoof, PRO bypass, enemy score, ban bypass, mass report
// ==UserScript==
// @name RSWARE — Omoggle 1v1
// @namespace http://tampermonkey.net/
// @version 9.3
// @description Score spoof, camera spoof, mic spoof, country spoof, PRO bypass, enemy score, ban bypass, mass report
// @author you
// @match *://*.omoggle.com/*
// @match *://omoggle.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
/* ══════════════════════════════════════════════════════════════════
STATE
══════════════════════════════════════════════════════════════════ */
const CFG = {
scoreSpoof: false, displayWin: false, liveSpoof: false,
scoreVal: 10, enemySpoof: false, enemyVal: 1.0,
};
const DYN = { enabled: false, min: 7.0, max: 10.0, cur: 10.0, timer: null };
const CAM = { enabled: false, mode: 'black', img: null, color: '#111111' };
const MIC = { enabled: false, mode: 'silent', freq: 440 };
const SPF = { country: false, code: 'US', pro: false, ban: false };
const BAN_STATE = { lastUrl: null, lastProto: null, reconnectTimer: null };
const RPT = { running: false, sent: 0, total: 0, timer: null, lastUrl: null, lastHeaders: {}, lastBody: null };
const MATCH = { opponentId: null, opponentUsername: null, matchId: null };
/* ══════════════════════════════════════════════════════════════════
CONFIG PERSISTENCE
══════════════════════════════════════════════════════════════════ */
const CONF_KEY = 'rsware_omoggle_v93';
let _saveTimer = null;
function saveConf() {
try {
localStorage.setItem(CONF_KEY, JSON.stringify({
v: 93,
score: { spoof: CFG.scoreSpoof, val: CFG.scoreVal, displayWin: CFG.displayWin, liveSpoof: CFG.liveSpoof,
dynEnabled: DYN.enabled, dynMin: DYN.min, dynMax: DYN.max, dynInt: 3 },
enemy: { spoof: CFG.enemySpoof, val: CFG.enemyVal },
cam: { enabled: CAM.enabled, mode: CAM.mode, color: CAM.color },
mic: { enabled: MIC.enabled, mode: MIC.mode, freq: MIC.freq },
spf: { country: SPF.country, code: SPF.code, pro: SPF.pro, ban: SPF.ban },
}));
const el = document.getElementById('rsw-conf-status');
if (el) { el.textContent = 'SAVED'; el.style.color = '#0f0'; setTimeout(function(){ if(el){el.textContent='AUTO';el.style.color='#333';} }, 1400); }
} catch(_) {}
}
function autoSave() { clearTimeout(_saveTimer); _saveTimer = setTimeout(saveConf, 700); }
function loadConf() { try { const r=localStorage.getItem(CONF_KEY); return r?JSON.parse(r):null; } catch(_){ return null; } }
function applyConf(c) {
if (!c || c.v !== 93) return;
if (c.score) {
CFG.scoreSpoof = !!c.score.spoof;
CFG.scoreVal = c.score.val != null ? c.score.val : 10;
CFG.displayWin = !!c.score.displayWin;
CFG.liveSpoof = !!c.score.liveSpoof;
DYN.enabled = !!c.score.dynEnabled;
DYN.min = c.score.dynMin != null ? c.score.dynMin : 7;
DYN.max = c.score.dynMax != null ? c.score.dynMax : 10;
}
if (c.enemy) { CFG.enemySpoof = !!c.enemy.spoof; CFG.enemyVal = c.enemy.val != null ? c.enemy.val : 1.0; }
if (c.cam) { CAM.enabled = !!c.cam.enabled; CAM.mode = c.cam.mode||'black'; CAM.color = c.cam.color||'#111111'; }
if (c.mic) { MIC.enabled = !!c.mic.enabled; MIC.mode = c.mic.mode||'silent'; MIC.freq = c.mic.freq||440; }
if (c.spf) { SPF.country = !!c.spf.country; SPF.code = c.spf.code||'US'; SPF.pro = !!c.spf.pro; SPF.ban = !!c.spf.ban; }
}
// Restore saved state before hooks run
applyConf(loadConf());
let hookedWsRef = null, floodTimer = null, pktCount = 0;
const logs = [];
function log(msg) {
const t = new Date().toLocaleTimeString('en-GB', { hour12: false });
logs.push(t + ' ' + msg);
if (logs.length > 400) logs.shift();
const el = document.getElementById('rsw-log');
if (el) { el.textContent = logs.join('\n'); el.scrollTop = el.scrollHeight; }
const c = document.getElementById('rsw-cnt');
if (c) c.textContent = pktCount;
}
/* ══════════════════════════════════════════════════════════════════
DYNAMIC RANGE
══════════════════════════════════════════════════════════════════ */
function dynGet() { return DYN.enabled ? DYN.cur : CFG.scoreVal; }
function dynTick() {
DYN.cur = Math.round((DYN.min + Math.random() * (DYN.max - DYN.min)) * 10) / 10;
// UI is updated by the 250ms ticker — no DOM access needed here
log('[DYN] tick → ' + DYN.cur.toFixed(1));
}
function dynStart(ms) {
if (DYN.timer) clearInterval(DYN.timer);
dynTick(); // fire immediately so first value is set right away
DYN.timer = setInterval(dynTick, Math.max(100, ms));
}
function dynStop() {
if (DYN.timer) { clearInterval(DYN.timer); DYN.timer = null; }
DYN.cur = CFG.scoreVal;
}
/* ══════════════════════════════════════════════════════════════════
CAMERA CANVAS — set up at document-start before MediaPipe loads
══════════════════════════════════════════════════════════════════ */
const camCanvas = document.createElement('canvas');
camCanvas.width = 640; camCanvas.height = 480;
const camCtx = camCanvas.getContext('2d');
(function camLoop() {
if (CAM.mode === 'image' && CAM.img) {
camCtx.drawImage(CAM.img, 0, 0, 640, 480);
} else {
camCtx.fillStyle = CAM.color || '#000';
camCtx.fillRect(0, 0, 640, 480);
}
const prev = document.getElementById('rsw-cam-prev');
if (prev) {
const pc = prev.getContext('2d');
pc.drawImage(camCanvas, 0, 0, prev.width, prev.height);
}
requestAnimationFrame(camLoop);
})();
/* getUserMedia hook */
let _micCtx = null;
// keep hard refs so AudioNodes aren't GC'd
const _micNodes = [];
function createMicTrack() {
// Resume or create AudioContext — must happen inside user-gesture chain (getUserMedia satisfies this)
if (!_micCtx) {
_micCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (_micCtx.state === 'suspended') _micCtx.resume();
const dst = _micCtx.createMediaStreamDestination();
_micNodes.length = 0; // clear old nodes
if (MIC.mode === 'tone') {
const osc = _micCtx.createOscillator();
const gain = _micCtx.createGain();
osc.type = 'sine';
osc.frequency.value = MIC.freq || 440;
gain.gain.value = 0.4;
osc.connect(gain); gain.connect(dst);
osc.start();
_micNodes.push(osc, gain, dst);
log('[MIC] tone track active ' + (MIC.freq || 440) + 'Hz');
} else if (MIC.mode === 'noise') {
// White noise via ScriptProcessor (works in all browsers)
const bufSize = 2048;
const sp = _micCtx.createScriptProcessor(bufSize, 0, 1);
sp.onaudioprocess = function (e) {
const out = e.outputBuffer.getChannelData(0);
for (let i = 0; i < bufSize; i++) out[i] = (Math.random() * 2 - 1) * 0.35;
};
const gain = _micCtx.createGain(); gain.gain.value = 1;
sp.connect(gain); gain.connect(dst);
_micNodes.push(sp, gain, dst);
log('[MIC] noise track active');
} else {
// silent — oscillator at 0 gain
const osc = _micCtx.createOscillator();
const gain = _micCtx.createGain(); gain.gain.value = 0;
osc.connect(gain); gain.connect(dst);
osc.start();
_micNodes.push(osc, gain, dst);
log('[MIC] silent track active');
}
const track = dst.stream.getAudioTracks()[0];
if (!track) { log('[MIC] ERROR: no audio track returned'); }
return track;
}
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const _gum = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = async function (constraints) {
let real;
try { real = await _gum(constraints); } catch (e) { throw e; }
if (!CAM.enabled && !MIC.enabled) return real;
const tracks = [];
if (constraints && constraints.video) {
if (CAM.enabled) {
real.getVideoTracks().forEach(t => t.stop());
tracks.push(...camCanvas.captureStream(30).getVideoTracks());
log('[CAM] video track spoofed mode=' + CAM.mode);
} else { tracks.push(...real.getVideoTracks()); }
}
if (constraints && constraints.audio) {
if (MIC.enabled) {
real.getAudioTracks().forEach(t => t.stop());
const t = createMicTrack();
if (t) tracks.push(t);
} else { tracks.push(...real.getAudioTracks()); }
}
// If no tracks were added (e.g. site only asked video and cam is off) fall back
if (tracks.length === 0) return real;
return new MediaStream(tracks);
};
}
/* ══════════════════════════════════════════════════════════════════
COUNTRY SPOOF
══════════════════════════════════════════════════════════════════ */
const COUNTRIES = {
US:{ name:'United States', lang:'en-US', tz:'America/New_York', lat:40.71, lon:-74.01 },
GB:{ name:'United Kingdom',lang:'en-GB', tz:'Europe/London', lat:51.51, lon:-0.13 },
DE:{ name:'Germany', lang:'de-DE', tz:'Europe/Berlin', lat:52.52, lon:13.40 },
FR:{ name:'France', lang:'fr-FR', tz:'Europe/Paris', lat:48.86, lon:2.35 },
JP:{ name:'Japan', lang:'ja-JP', tz:'Asia/Tokyo', lat:35.69, lon:139.69 },
KR:{ name:'South Korea', lang:'ko-KR', tz:'Asia/Seoul', lat:37.57, lon:126.98 },
CN:{ name:'China', lang:'zh-CN', tz:'Asia/Shanghai', lat:31.23, lon:121.47 },
BR:{ name:'Brazil', lang:'pt-BR', tz:'America/Sao_Paulo', lat:-23.55,lon:-46.63 },
RU:{ name:'Russia', lang:'ru-RU', tz:'Europe/Moscow', lat:55.75, lon:37.62 },
IN:{ name:'India', lang:'hi-IN', tz:'Asia/Kolkata', lat:28.61, lon:77.21 },
AU:{ name:'Australia', lang:'en-AU', tz:'Australia/Sydney', lat:-33.87,lon:151.21 },
CA:{ name:'Canada', lang:'en-CA', tz:'America/Toronto', lat:43.65, lon:-79.38 },
IT:{ name:'Italy', lang:'it-IT', tz:'Europe/Rome', lat:41.90, lon:12.50 },
ES:{ name:'Spain', lang:'es-ES', tz:'Europe/Madrid', lat:40.42, lon:-3.70 },
NL:{ name:'Netherlands', lang:'nl-NL', tz:'Europe/Amsterdam', lat:52.37, lon:4.90 },
SE:{ name:'Sweden', lang:'sv-SE', tz:'Europe/Stockholm', lat:59.33, lon:18.07 },
NO:{ name:'Norway', lang:'nb-NO', tz:'Europe/Oslo', lat:59.91, lon:10.75 },
PL:{ name:'Poland', lang:'pl-PL', tz:'Europe/Warsaw', lat:52.23, lon:21.01 },
TR:{ name:'Turkey', lang:'tr-TR', tz:'Europe/Istanbul', lat:39.93, lon:32.86 },
MX:{ name:'Mexico', lang:'es-MX', tz:'America/Mexico_City',lat:19.43, lon:-99.13 },
};
function applyCountrySpoof() {
const c = COUNTRIES[SPF.code] || COUNTRIES.US;
try {
Object.defineProperty(navigator, 'language', { configurable:true, get:()=>c.lang });
Object.defineProperty(navigator, 'languages', { configurable:true, get:()=>[c.lang, c.lang.split('-')[0]] });
} catch(_) {}
try {
if (navigator.geolocation) {
const _og = navigator.geolocation.getCurrentPosition.bind(navigator.geolocation);
navigator.geolocation.getCurrentPosition = function (ok, err, opts) {
if (SPF.country) ok({ coords:{ latitude:c.lat, longitude:c.lon, accuracy:50, altitude:null, altitudeAccuracy:null, heading:null, speed:null }, timestamp:Date.now() });
else _og(ok, err, opts);
};
}
} catch(_) {}
log('[SPF] country → ' + c.name);
}
/* ══════════════════════════════════════════════════════════════════
PRO BYPASS — 4-layer attack
1. fetch() hook — patches API responses before JS sees them
2. XHR hook — same for XMLHttpRequest
3. localStorage hook — patches cached Supabase session on every read
4. CSS injection — removes paywall blur/lock overlays from DOM
══════════════════════════════════════════════════════════════════ */
// Supabase project ID → the localStorage key they use for the session
const SB_LS_KEY = 'sb-echqrtrlzdhzyuhtueoj-auth-token';
// Deep-patch any JS object to look like a PRO user.
// Only touches fields that are clearly subscription/plan related.
function deepPatchPro(o, depth) {
if (!o || typeof o !== 'object' || depth > 6) return;
depth = (depth || 0);
// Always unconditionally set known PRO fields
o.is_pro = true;
o.isPro = true;
o.hasPro = true;
o.pro = true;
o.plan = 'pro';
o.tier = 'pro';
o.role = o.role || 'authenticated'; // don't destroy real role
o.subscription = { plan:'pro', status:'active', active:true,
cancel_at_period_end:false, current_period_end: 9999999999 };
// Supabase user_metadata / app_metadata — create if missing
if (!o.user_metadata || typeof o.user_metadata !== 'object') o.user_metadata = {};
o.user_metadata.plan = 'pro';
o.user_metadata.is_pro = true;
o.user_metadata.tier = 'pro';
if (!o.app_metadata || typeof o.app_metadata !== 'object') o.app_metadata = {};
o.app_metadata.plan = 'pro';
o.app_metadata.is_pro = true;
// Recurse into common wrapper keys only (avoid recursing everything)
['data','user','profile','account','session','me','viewer'].forEach(function(k) {
if (o[k] && typeof o[k] === 'object') deepPatchPro(o[k], depth + 1);
});
if (Array.isArray(o)) o.forEach(function(item) { deepPatchPro(item, depth + 1); });
}
/* ══════════════════════════════════════════════════════════════════
BAN BYPASS — strip ban fields from any object
══════════════════════════════════════════════════════════════════ */
const BAN_WORDS = ['ban','suspend','restrict','block','mute','shadow'];
function isBanMsg(s) {
if (!s || typeof s !== 'string') return false;
const lc = s.toLowerCase();
return BAN_WORDS.some(function(w) { return lc.includes(w); });
}
function deepPatchBan(o, depth) {
if (!o || typeof o !== 'object' || depth > 6) return;
// Wipe every ban-related flag unconditionally
o.banned = false; o.is_banned = false; o.isBanned = false;
o.suspended = false; o.is_suspended = false; o.isSuspended = false;
o.restricted = false; o.is_restricted = false; o.isRestricted = false;
o.blocked = false; o.is_blocked = false;
o.shadow_banned = false; o.shadowBanned = false;
o.banned_at = null; o.ban_expires = null;
o.banned_until = null; o.suspend_until = null;
o.ban_reason = null; o.banReason = null;
o.suspension_reason = null;
// Status fields: turn "banned"/"suspended" → "active"
if (typeof o.status === 'string' && isBanMsg(o.status)) o.status = 'active';
if (typeof o.state === 'string' && isBanMsg(o.state)) o.state = 'active';
if (typeof o.account_status === 'string' && isBanMsg(o.account_status)) o.account_status = 'active';
// Recurse into common wrapper keys
['data','user','profile','account','session','me','viewer'].forEach(function(k) {
if (o[k] && typeof o[k] === 'object') deepPatchBan(o[k], depth + 1);
});
if (Array.isArray(o)) o.forEach(function(item) { deepPatchBan(item, depth + 1); });
}
/* ══════════════════════════════════════════════════════════════════
OPPONENT DETECTION
══════════════════════════════════════════════════════════════════ */
const OPP_ID_FIELDS = ['opponent_id','opponentId','opponent_user_id','challengerId','player2_id','p2_id','p2Id','enemy_id','enemyId','other_user_id','otherUserId'];
const OPP_NAME_FIELDS = ['opponent_username','opponentUsername','opponent_name','opponentName','challengerUsername','p2Username','p2_username','enemyUsername','enemy_username','other_username','otherUsername','opponent_display_name'];
const MATCH_ID_FIELDS = ['match_id','matchId','room_id','roomId','game_id','gameId','battle_id','session_id'];
function updateOpponentUI() {
const val = MATCH.opponentUsername || MATCH.opponentId || '--';
const el = document.getElementById('rsw-rpt-opp-val'); if (el) { el.textContent = val; el.style.color = val !== '--' ? '#0f0' : '#444'; }
const el2 = document.getElementById('rsw-rpt-opp-id'); if (el2) el2.textContent = MATCH.opponentId || '--';
// Auto-fill report target if currently empty
const tgt = document.getElementById('rsw-rpt-target');
if (tgt && !tgt.value && (MATCH.opponentId || MATCH.opponentUsername))
tgt.value = MATCH.opponentId || MATCH.opponentUsername;
}
function getMyId() {
try {
for (let i=0;i<localStorage.length;i++) {
const k=localStorage.key(i); if(!k) continue;
if (k.includes('auth-token')||k.includes('supabase')||k.startsWith('sb-')) {
const obj=JSON.parse(localStorage.getItem(k)||'');
const user=obj&&(obj.user||obj.session&&obj.session.user);
if (user&&user.id) return user.id;
}
}
} catch(_){}
return null;
}
function captureOpponent(obj, depth) {
if (!obj || typeof obj !== 'object' || depth > 8) return;
const me = CFG.myUsername;
const myId = depth === 0 ? getMyId() : null; // only fetch from LS at top level
// Direct opponent_* fields
for (const f of OPP_ID_FIELDS) {
if (obj[f] && typeof obj[f] === 'string' && obj[f].length > 2) {
if (MATCH.opponentId !== obj[f]) { MATCH.opponentId = obj[f]; log('[MATCH] opp ID ('+f+'): '+obj[f]); updateOpponentUI(); }
break;
}
}
for (const f of OPP_NAME_FIELDS) {
if (obj[f] && typeof obj[f] === 'string' && obj[f].length > 0) {
if (MATCH.opponentUsername !== obj[f]) { MATCH.opponentUsername = obj[f]; log('[MATCH] opp name ('+f+'): '+obj[f]); updateOpponentUI(); }
break;
}
}
// player1_id / player2_id pattern — figure out which one is us
const p1id = obj.player1_id || obj.p1_id || obj.p1Id;
const p2id = obj.player2_id || obj.p2_id || obj.p2Id;
const p1name= obj.player1_username || obj.player1_name || obj.p1_username || obj.p1Username;
const p2name= obj.player2_username || obj.player2_name || obj.p2_username || obj.p2Username;
if (p1id || p2id || p1name || p2name) {
const _myId = myId || getMyId();
const iAm1 = (me && (p1name===me||p1id===me)) || (_myId && (p1id===_myId));
const iAm2 = (me && (p2name===me||p2id===me)) || (_myId && (p2id===_myId));
if (iAm1) {
// I am player1 → opponent is player2
if (p2id && !MATCH.opponentId) { MATCH.opponentId = String(p2id); log('[MATCH] opp ID (player2_id): '+p2id); updateOpponentUI(); }
if (p2name&& !MATCH.opponentUsername) { MATCH.opponentUsername=String(p2name); log('[MATCH] opp name (player2): '+p2name); updateOpponentUI(); }
} else if (iAm2) {
// I am player2 → opponent is player1
if (p1id && !MATCH.opponentId) { MATCH.opponentId = String(p1id); log('[MATCH] opp ID (player1_id): '+p1id); updateOpponentUI(); }
if (p1name&& !MATCH.opponentUsername) { MATCH.opponentUsername=String(p1name); log('[MATCH] opp name (player1): '+p1name); updateOpponentUI(); }
} else {
// Can't tell which is us — store player2 as a best-guess (challenger is usually p2)
if (!MATCH.opponentId) {
const guess = p2id || p1id;
if (guess) { MATCH.opponentId=String(guess); log('[MATCH] opp ID (guess p2): '+guess); updateOpponentUI(); }
}
if (!MATCH.opponentUsername) {
const guess = p2name || p1name;
if (guess) { MATCH.opponentUsername=String(guess); log('[MATCH] opp name (guess p2): '+guess); updateOpponentUI(); }
}
}
}
// players[] array — find entry that isn't us
if (Array.isArray(obj.players)) {
const _myId = myId || getMyId();
for (const p of obj.players) {
if (!p||typeof p!=='object') continue;
const pName=p.username||p.name||p.display_name||p.displayName;
const pId =p.id||p.user_id||p.userId;
if (me && (pName===me||pId===me)) continue;
if (_myId&& pId===_myId) continue;
if (pId &&!MATCH.opponentId) { MATCH.opponentId=String(pId); log('[MATCH] opp ID (players[]): '+pId); updateOpponentUI(); }
if (pName &&!MATCH.opponentUsername) { MATCH.opponentUsername=String(pName); log('[MATCH] opp name (players[]): '+pName); updateOpponentUI(); }
}
}
for (const f of MATCH_ID_FIELDS) { if (obj[f]) MATCH.matchId=String(obj[f]); }
// Recurse — include Supabase Realtime nesting: record / new_record / response
const recurse=['payload','data','match','room','game','battle','session','meta','record','new_record','response','body'];
for (const k of recurse) { if (obj[k]&&typeof obj[k]==='object') captureOpponent(obj[k],depth+1); }
}
function scanDOMForOpponent() {
const me = CFG.myUsername;
// Try CSS class hints first
const hints = ['[class*="opponent"]','[class*="enemy"]','[class*="player2"]','[class*="p2"]',
'[class*="challenger"]','[data-player="2"]','[data-role="opponent"]','[class*="other-player"]'];
for (const sel of hints) {
try {
document.querySelectorAll(sel).forEach(function(el) {
const txt = (el.textContent || '').trim();
if (txt && txt.length > 1 && txt.length < 50 && txt !== me && !/^\d/.test(txt)) {
if (!MATCH.opponentUsername) { MATCH.opponentUsername = txt; log('[DOM] opponent from "'+sel+'": '+txt); updateOpponentUI(); }
}
});
} catch(_) {}
}
// Walk every text node — find anything that looks like a username that isn't ours
try {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
while (walker.nextNode()) {
const txt = (walker.currentNode.textContent || '').trim();
if (!txt || txt.length < 2 || txt.length > 40) continue;
const parent = walker.currentNode.parentElement;
if (!parent) continue;
const tag = parent.tagName.toLowerCase();
if (['script','style','button','input','meta'].includes(tag)) continue;
const cls = (parent.className || '').toLowerCase();
if ((cls.includes('name')||cls.includes('user')||cls.includes('player')||cls.includes('profile'))
&& txt !== me && !txt.includes(' ') === false && !/^\d+\.?\d*$/.test(txt)) {
if (!MATCH.opponentUsername && txt !== me) {
MATCH.opponentUsername = txt;
log('[DOM] opponent from text scan: ' + txt);
updateOpponentUI();
break;
}
}
}
} catch(_) {}
log('[DOM] scan done — opp: '+(MATCH.opponentUsername||'not found')+' id: '+(MATCH.opponentId||'not found'));
}
/* Layer 1 — fetch hook (fires before site JS reads the response) */
const _origFetch = window.fetch;
window.fetch = async function (...args) {
const url = String(args[0] instanceof Request ? args[0].url : (args[0] || ''));
let reqInit = (args.length > 1 && args[1]) ? args[1] : {};
const reqMethod = ((args[0] instanceof Request ? args[0].method : reqInit.method) || 'GET').toUpperCase();
// ── Score: patch outgoing POST/PUT bodies before sending ──────────────
if (CFG.scoreSpoof && (reqMethod==='POST'||reqMethod==='PUT') && reqInit.body && typeof reqInit.body==='string') {
try {
const j = JSON.parse(reqInit.body);
if (j && typeof j==='object' && injectScoreFields(j)) {
reqInit = Object.assign({}, reqInit, { body: JSON.stringify(j) });
if (args.length > 1) args = [args[0], reqInit];
else args = [args[0], reqInit];
log('[->FETCH] score in body → '+dynGet()+' '+url.replace(/^https?:\/\/[^/]+/,'').slice(0,45));
}
} catch(_) {}
}
// Capture report requests so mass-report can replay them exactly
if (reqMethod === 'POST' && /report/i.test(url)) {
try {
const b = reqInit.body ? String(reqInit.body) : null;
RPT.lastUrl = url; RPT.lastBody = b;
RPT.lastHeaders = (reqInit.headers && typeof reqInit.headers === 'object')
? Object.assign({}, reqInit.headers) : {};
log('[RPT] captured: ' + url.replace(/^https?:\/\/[^/]+/,'').slice(0,55));
const el = document.getElementById('rsw-rpt-captured');
if (el) { el.textContent = url.replace(/^https?:\/\/[^/]+/,'').slice(0,42); el.style.color='#0f0'; }
const be = document.getElementById('rsw-rpt-body');
if (be && b) { be.textContent = b.slice(0,60); be.style.color='#888'; }
} catch(_) {}
}
const resp = await _origFetch(...args);
if (!SPF.pro && !SPF.ban) return resp;
// ── BAN: intercept ban responses (any non-200 with ban text in body) ──
if (SPF.ban && resp.status >= 400) {
try {
const body = await resp.clone().text();
if (isBanMsg(body)) {
log('[BAN] ' + resp.status + ' ban response blocked → faking 200: ' + url.replace(/^https?:\/\/[^/]+/,'').slice(0,50));
// For Supabase auth refresh (/auth/v1/token), return a fake success
// that keeps the existing token so the SDK doesn't sign us out
const fakeBody = url.includes('/auth/') || url.includes('token')
? '{"access_token":"","token_type":"bearer","expires_in":3600,"expires_at":' + (Math.floor(Date.now()/1000)+3600) + ',"refresh_token":""}'
: '{"success":true,"data":{}}';
return new Response(fakeBody, {
status: 200, statusText: 'OK', headers: resp.headers
});
}
} catch(_) {}
}
const ct = resp.headers.get('content-type') || '';
if (!ct.includes('json')) return resp;
// patch ALL json from supabase AND omoggle (broad)
// Always scan every JSON response for opponent data, even on unknown URLs
const isKnown = url.includes('supabase') || url.includes('omoggle.com') || url.includes('/api/');
try {
const text = await resp.clone().text();
const j = JSON.parse(text);
captureOpponent(j, 0);
if (isKnown && (SPF.pro || SPF.ban)) {
if (SPF.pro) deepPatchPro(j, 0);
if (SPF.ban) deepPatchBan(j, 0);
log('[PATCH] fetch: ' + url.replace(/^https?:\/\/[^/]+/, '').slice(0, 50));
return new Response(JSON.stringify(j), {
status: resp.status, statusText: resp.statusText, headers: resp.headers
});
}
} catch(_) {}
return resp;
};
/* Layer 2 — XHR hook */
(function() {
const _open = XMLHttpRequest.prototype.open;
const _send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(m, url) {
this._rsw_url = String(url || '');
return _open.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
// Patch outgoing score fields in XHR request body
if (CFG.scoreSpoof && body && typeof body==='string') {
try {
const j = JSON.parse(body);
if (j && typeof j==='object' && injectScoreFields(j)) {
body = JSON.stringify(j);
log('[->XHR] score in body → '+dynGet()+' '+(this._rsw_url||'').slice(-45));
}
} catch(_) {}
}
if ((SPF.pro || SPF.ban) && this._rsw_url) {
const xhr = this;
xhr.addEventListener('load', function() {
if (!SPF.pro && !SPF.ban) return;
try {
const ct = xhr.getResponseHeader('content-type') || '';
if (!ct.includes('json')) return;
const j = JSON.parse(xhr.responseText);
if (SPF.pro) deepPatchPro(j, 0);
if (SPF.ban) deepPatchBan(j, 0);
Object.defineProperty(xhr, 'responseText', { configurable:true, get: function(){ return JSON.stringify(j); } });
Object.defineProperty(xhr, 'response', { configurable:true, get: function(){ return JSON.stringify(j); } });
log('[PATCH] XHR: ' + xhr._rsw_url.slice(-50));
} catch(_) {}
});
}
// Pass the (possibly modified) body
return body !== arguments[0] ? _send.call(this, body) : _send.apply(this, arguments);
};
})();
/* Layer 3 — localStorage hook: patch Supabase cached session on every READ and WRITE */
(function() {
const _origGet = Storage.prototype.getItem;
const _origSet = Storage.prototype.setItem;
function isSbKey(key) {
return key && (key === SB_LS_KEY || key.includes('auth-token') || key.includes('supabase') || key.startsWith('sb-'));
}
function patchSbObj(obj) {
if (SPF.pro) {
if (obj.user && typeof obj.user === 'object') deepPatchPro(obj.user, 0);
if (obj.user_metadata || obj.app_metadata || obj.email) deepPatchPro(obj, 0);
}
if (SPF.ban) {
if (obj.user && typeof obj.user === 'object') deepPatchBan(obj.user, 0);
deepPatchBan(obj, 0);
}
}
// Patch on READ
Storage.prototype.getItem = function(key) {
const val = _origGet.call(this, key);
if (!SPF.pro && !SPF.ban) return val;
if (!val || typeof val !== 'string') return val;
if (isSbKey(key)) {
try {
const obj = JSON.parse(val);
if (obj && typeof obj === 'object') { patchSbObj(obj); return JSON.stringify(obj); }
} catch(_) {}
}
return val;
};
// Patch on WRITE — Supabase SDK stores fresh token after every refresh
// This is the key fix: strip banned_until BEFORE it gets written back
Storage.prototype.setItem = function(key, val) {
if ((SPF.pro || SPF.ban) && isSbKey(key) && val && typeof val === 'string') {
try {
const obj = JSON.parse(val);
if (obj && typeof obj === 'object') {
patchSbObj(obj);
val = JSON.stringify(obj);
if (SPF.ban) log('[BAN] setItem patched ban fields before write: ' + key.slice(-30));
}
} catch(_) {}
}
return _origSet.call(this, key, val);
};
})();
/* Ban DOM scrubber — removes ban overlay elements as they appear */
function setupBanDOMScrubber() {
const BAN_TEXT_PATTERNS = [/you.{0,8}(banned|suspended|restricted)/i, /account.{0,12}(ban|suspend)/i,
/temporarily banned/i, /permanently banned/i, /ban expires/i];
const BAN_CSS_PATTERNS = ['ban','suspend','restrict','blocked','shadowban'];
function scrubNode(node) {
if (!node || node.nodeType !== 1) return;
// Check text content
const txt = node.textContent || '';
if (BAN_TEXT_PATTERNS.some(r => r.test(txt)) && txt.length < 600) {
const tag = node.tagName.toLowerCase();
// Don't remove body/html/main — only overlay-like divs/sections
if (['div','section','aside','article','p','span','h1','h2','h3'].includes(tag)) {
node.style.display = 'none';
log('[BAN] DOM: hid ban node <' + tag + '> "' + txt.slice(0,40) + '"');
}
}
// Check CSS class/id for ban words
const cls = (node.className || '') + ' ' + (node.id || '');
if (BAN_CSS_PATTERNS.some(w => cls.toLowerCase().includes(w))) {
node.style.display = 'none';
node.style.visibility = 'hidden';
log('[BAN] DOM: hid ban-class element .' + (node.className||'').split(' ')[0]);
}
}
const obs = new MutationObserver(function(muts) {
if (!SPF.ban) return;
muts.forEach(function(m) {
m.addedNodes.forEach(function(n) { scrubNode(n); });
});
});
obs.observe(document.documentElement, { childList: true, subtree: true });
}
/* Layer 4 — CSS: remove common paywall/lock/upgrade overlays */
function injectProCSS() {
if (document.getElementById('rsw-pro-css')) return;
const s = document.createElement('style');
s.id = 'rsw-pro-css';
s.textContent = [
/* nuke upgrade banners / pro-lock overlays */
'[data-pro],[data-locked],[data-upgrade],[class*="ProBadge"],',
'[class*="pro-badge"],[class*="upgrade-banner"],[class*="paywall"],',
'[class*="ProLock"],[class*="pro-lock"],[class*="PremiumBadge"],',
'[class*="premium-badge"]{display:none!important}',
/* remove blur/grayscale from locked content */
'[class*="blurred"],[class*="locked"],[class*="restricted"],',
'[class*="blur-"],[class*="ProGate"],[class*="pro-gate"]',
'{filter:none!important;pointer-events:auto!important;',
'user-select:auto!important;opacity:1!important}',
/* ensure disabled buttons from free plan are clickable */
'[data-pro-required],[disabled][class*="pro"]{',
'pointer-events:auto!important;opacity:1!important;cursor:pointer!important}',
].join('');
(document.head || document.documentElement).appendChild(s);
log('[PRO] CSS overlay removal injected');
}
/* ══════════════════════════════════════════════════════════════════
IDEAL FACE — v8.0 unchanged
══════════════════════════════════════════════════════════════════ */
const IDEAL_FACE = {
10:[0.500,0.150], 152:[0.500,0.850], 234:[0.228,0.520], 454:[0.772,0.520],
33:[0.343,0.406], 133:[0.428,0.400], 159:[0.386,0.392], 145:[0.386,0.414],
362:[0.572,0.406], 263:[0.657,0.394], 386:[0.614,0.389], 374:[0.614,0.411],
0:[0.500,0.619],
172:[0.262,0.760], 397:[0.738,0.760], 150:[0.268,0.740], 379:[0.732,0.740],
171:[0.270,0.750], 396:[0.730,0.750], 70:[0.320,0.360], 300:[0.680,0.360],
63:[0.335,0.355], 293:[0.665,0.355], 105:[0.380,0.350], 334:[0.620,0.350],
46:[0.300,0.370], 276:[0.700,0.370], 116:[0.295,0.430], 345:[0.705,0.430],
123:[0.340,0.430], 352:[0.660,0.430], 50:[0.305,0.380], 280:[0.695,0.380],
187:[0.260,0.570], 411:[0.740,0.570], 132:[0.345,0.460], 361:[0.655,0.460],
174:[0.275,0.680], 399:[0.725,0.680], 136:[0.285,0.720], 365:[0.715,0.720],
148:[0.265,0.730], 377:[0.735,0.730], 176:[0.275,0.710], 401:[0.725,0.710],
58:[0.290,0.680], 288:[0.710,0.680],
};
/* Layer 1 */
const _JS = JSON.stringify;
JSON.stringify = function (v, r, s) {
if (CFG.scoreSpoof && v && typeof v==='object' && v.type==='SCAN_STATE' && v.payload) {
v = { type:'SCAN_STATE', payload:{ ...v.payload, overall:dynGet(),
isFaceStraight:true, faceStatus:'perfect', scoringConfidence:1.0, scoringWarnings:[] }};
pktCount++; log('[P2P] SCAN_STATE → ' + dynGet());
}
return _JS(v, r, s);
};
/* Layer 4 — binary frame injection
Server formula: overall = round(10 × clamp(Z × X, 1.1, 10)) / 10
Z = scoring result from landmarks (IDEAL_FACE gives Z ≈ 9.976)
X = quality byte / 255
So to hit target T: quality = round( T / 9.976 × 255 )
This lets us produce ANY score 1.1–10.0 just by tuning the quality byte
while keeping IDEAL_FACE landmarks (all metric sub-scores stay 10). */
const IDEAL_Z = 9.976; // Z produced by IDEAL_FACE — verified mathematically
function scoreToQuality(target) {
// clamp to server's valid range [1.1, 10]
const t = Math.max(1.1, Math.min(10, target));
return Math.max(1, Math.min(255, Math.round((t / IDEAL_Z) * 255)));
}
const _sign = crypto.subtle.sign.bind(crypto.subtle);
Object.defineProperty(crypto.subtle, 'sign', { configurable:true, writable:true,
value: async function sign(alg, key, data) {
const nm = typeof alg==='string' ? alg : alg && alg.name;
if (CFG.scoreSpoof && nm==='HMAC' && data instanceof ArrayBuffer && data.byteLength>=17) {
const v = new DataView(data);
if (v.getUint8(0)===4) {
const target = dynGet(); // use current score (static or dynamic)
const qByte = scoreToQuality(target); // ← quality controls the final score
v.setUint8(15, qByte); // quality byte
v.setUint8(16, 2); // faceStatus = "perfect"
v.setFloat32(9, 1.0, true); // aspectRatio = 1.0
const n = v.getUint16(13, true);
if (data.byteLength >= 17+n*8) {
// Inject IDEAL_FACE landmarks (maximises Z so quality byte has full control)
for (const [idx,[x,y]] of Object.entries(IDEAL_FACE)) {
const i=parseInt(idx);
if (i<n) { v.setFloat32(17+i*8,x,true); v.setFloat32(17+i*8+4,y,true); }
}
pktCount++;
log('[!] frame target=' + target.toFixed(1) + ' q=' + qByte + ' lm=' + n
+ ' → ~' + (Math.round(IDEAL_Z*(qByte/255)*10)/10).toFixed(1));
}
}
}
return _sign(alg, key, data);
}
});
/* Flood */
const _sendMap = new WeakMap();
function startFlood() {
if (floodTimer) return; log('[FLOOD] started 1ms');
floodTimer = setInterval(function(){
if (!CFG.scoreSpoof||!hookedWsRef||hookedWsRef.readyState!==WebSocket.OPEN) return;
const fn=_sendMap.get(hookedWsRef); if(fn) fn(_JS({type:'score_update',payload:{score:dynGet()}}));
},1);
}
function stopFlood() { if(floodTimer){clearInterval(floodTimer);floodTimer=null;log('[FLOOD] stopped');} }
function readPkt(raw) {
if (typeof raw!=='string') return null;
try { const o=JSON.parse(raw); if(o&&typeof o.type==='string') return o; } catch(_){}
return null;
}
const SCORE_FIELDS = ['score','self_score','final_score','user_score','my_score','overall','rating','playerScore','p1_score','p2_score'];
function injectScoreFields(obj) {
if (!obj || typeof obj !== 'object') return false;
let ch = false;
SCORE_FIELDS.forEach(function(f) {
if (f in obj && typeof obj[f] === 'number' && obj[f] >= 0) { obj[f] = dynGet(); ch = true; }
});
return ch;
}
function patchOut(raw) {
if (!CFG.scoreSpoof) return raw;
// Try any JSON message, not just type-keyed ones
let p; try { p = JSON.parse(raw); if (!p||typeof p!=='object') p=null; } catch(_){ p=null; }
if (!p) return raw;
// Known types — explicit handling
if (p.type==='score_update' && p.payload) { p.payload.score=dynGet(); pktCount++; return _JS(p); }
if (p.type==='score_submit' && p.payload) {
p.payload.self_score=dynGet(); p.payload.score=dynGet(); pktCount++;
log('[->WS] score_submit → '+dynGet()); return _JS(p);
}
if ((p.type==='SCAN_STATE'||p.type==='scan_state') && p.payload) {
p.payload.overall=dynGet(); p.payload.score=dynGet();
p.payload.isFaceStraight=true; p.payload.faceStatus='perfect';
p.payload.scoringConfidence=1.0; p.payload.scoringWarnings=[];
pktCount++; return _JS(p);
}
if (p.type==='final_score' && p.payload) {
injectScoreFields(p.payload); pktCount++;
log('[->WS] final_score → '+dynGet()); return _JS(p);
}
if (p.type==='round_end' && p.payload) {
injectScoreFields(p.payload); pktCount++;
log('[->WS] round_end patched → '+dynGet()); return _JS(p);
}
// Generic: scan payload and root for any numeric score field
let ch = false;
if (p.payload && typeof p.payload==='object') ch = injectScoreFields(p.payload) || ch;
ch = injectScoreFields(p) || ch;
if (ch) { pktCount++; log('[->WS] score fields patched type='+(p.type||'?')+' → '+dynGet()); return _JS(p); }
return raw;
}
// Sentinel — returned by patchIn to tell the WS handler to DROP the message
const WS_DROP = '__rsw_drop__';
function patchIn(raw) {
if (typeof raw!=='string') return raw;
// Parse BEFORE readPkt so we capture from Supabase Realtime messages
// (which use "event" not "type" and would otherwise be skipped by readPkt)
try { const j=JSON.parse(raw); if(j&&typeof j==='object') captureOpponent(j,0); } catch(_){}
const p=readPkt(raw); if(!p) return raw;
// ── BAN BYPASS: drop ban/kick/suspend messages from server ──────────
if (SPF.ban) {
const banTypes = ['ban','banned','kick','kicked','suspend','suspended','restrict','restricted','shadow_ban'];
if (banTypes.includes(p.type) || (p.payload && isBanMsg(_JS(p.payload)))) {
log('[BAN] WS ban msg dropped: type=' + p.type);
return WS_DROP;
}
// Also strip ban fields from any incoming payload
if (p.payload && typeof p.payload === 'object') deepPatchBan(p.payload, 0);
}
if (!p.payload) return raw;
if (p.type==='live_score') {
log('[<-WS] live_score you='+(p.payload.your_score!=null?p.payload.your_score.toFixed(2):'?')
+' opp='+(p.payload.opponent_score!=null?p.payload.opponent_score.toFixed(2):'?'));
let ch=false;
if (CFG.liveSpoof) { p.payload.your_score=dynGet(); ch=true; }
if (CFG.enemySpoof) { p.payload.opponent_score=CFG.enemyVal; ch=true; }
return ch?_JS(p):raw;
}
if (p.type==='match_result') {
log('[<-WS] match_result result='+p.payload.result
+' you='+(p.payload.your_score!=null?p.payload.your_score.toFixed(1):'?')
+' opp='+(p.payload.opponent_score!=null?p.payload.opponent_score.toFixed(1):'?'));
let ch=false;
if (CFG.displayWin) { p.payload.result='win'; p.payload.your_score=p.payload.p1_score=dynGet(); ch=true; }
if (CFG.enemySpoof) { p.payload.opponent_score=p.payload.p2_score=CFG.enemyVal; ch=true; }
if (ch){pktCount++;return _JS(p);} return raw;
}
return raw;
}
/* WebSocket hook */
const _WS=window.WebSocket;
window.WebSocket=function(url,proto){
const ws=proto?new _WS(url,proto):new _WS(url);
if(url&&url.includes('omoggle.com')){
hookedWsRef=ws;
// Store URL + proto for ban-reconnect
BAN_STATE.lastUrl=url; BAN_STATE.lastProto=proto||undefined;
log('[WS] '+url.split('?')[0]);
if(CFG.scoreSpoof)startFlood();
}
const os=ws.send.bind(ws); _sendMap.set(ws,os);
ws.send=function(d){ if(typeof d==='string')d=patchOut(d); os(d); };
let _om=null;
Object.defineProperty(ws,'onmessage',{configurable:true,get:()=>_om,set:fn=>_om=fn});
ws.addEventListener('message',function(e){
if(!_om)return;
if(typeof e.data==='string'){
const p=patchIn(e.data);
if(p===WS_DROP)return; // drop ban message
if(p!==e.data){_om.call(ws,new MessageEvent('message',{data:p,origin:e.origin}));return;}
}
_om.call(ws,e);
});
const oa=ws.addEventListener.bind(ws);
ws.addEventListener=function(type,fn,opts){
if(type==='message'){oa('message',function(e){
if(typeof e.data!=='string'){fn.call(ws,e);return;}
const p=patchIn(e.data);
if(p===WS_DROP)return; // drop ban message
fn.call(ws,p!==e.data?new MessageEvent('message',{data:p,origin:e.origin}):e);
},opts);}else{oa(type,fn,opts);}
};
ws.addEventListener('close',function(e){
if(hookedWsRef===ws){hookedWsRef=null;stopFlood();}
// BAN auto-reconnect: if close reason mentions a ban, reconnect
if(SPF.ban && BAN_STATE.lastUrl){
const reason = (e.reason||'').toLowerCase();
const code = e.code;
if(isBanMsg(reason) || code===4003 || code===4429){
log('[BAN] WS closed — ban detected (code='+code+' reason="'+e.reason+'") — reconnecting in 1.5s');
clearTimeout(BAN_STATE.reconnectTimer);
BAN_STATE.reconnectTimer=setTimeout(function(){
log('[BAN] reconnecting → '+BAN_STATE.lastUrl.split('?')[0]);
window.WebSocket(BAN_STATE.lastUrl, BAN_STATE.lastProto);
},1500);
}
}
});
return ws;
};
Object.assign(window.WebSocket,_WS); window.WebSocket.prototype=_WS.prototype;
/* ══════════════════════════════════════════════════════════════════
UI — RSWARE 900×900 black/grey/white
══════════════════════════════════════════════════════════════════ */
const CSS = `
#rsw{position:fixed;top:30px;left:30px;width:900px;height:900px;
background:#0c0c0c;border:1px solid #2a2a2a;border-radius:6px;
box-shadow:0 24px 80px rgba(0,0,0,.95);z-index:2147483647;
font-family:'Courier New',monospace;color:#d0d0d0;user-select:none;
display:flex;flex-direction:column;overflow:hidden}
#rsw.h{display:none}
/* ── title bar ── */
#rsw-bar{background:#111;border-bottom:1px solid #222;height:52px;
display:flex;align-items:center;padding:0 18px;gap:14px;cursor:move;flex-shrink:0}
#rsw-logo{font-family:'Arial Black',Arial,sans-serif;font-size:22px;font-weight:900;
color:#fff;letter-spacing:5px}
#rsw-ver{font-size:9px;color:#444;letter-spacing:1px;margin-top:2px}
#rsw-spacer{flex:1}
#rsw-status{font-size:9px;color:#444;letter-spacing:2px}
.rsw-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;
background:#161616;border:1px solid #2a2a2a;border-radius:3px;
font-size:9px;letter-spacing:1px;color:#555}
.rsw-pill.on{border-color:#3a3a3a;color:#0f0}
.rsw-pill.on::before{content:'';width:6px;height:6px;border-radius:50%;background:#0f0;display:inline-block}
#rsw-close{width:24px;height:24px;background:#1a1a1a;border:1px solid #333;
color:#666;font-size:14px;cursor:pointer;border-radius:3px;
display:flex;align-items:center;justify-content:center;flex-shrink:0}
#rsw-close:hover{background:#200;border-color:#f44;color:#f44}
/* ── tab bar ── */
#rsw-tabs{display:flex;background:#0f0f0f;border-bottom:1px solid #1e1e1e;
flex-shrink:0;height:40px}
.rsw-tab{flex:1;display:flex;align-items:center;justify-content:center;
font-size:9px;font-weight:900;letter-spacing:2px;color:#444;
cursor:pointer;border-right:1px solid #1a1a1a;transition:all .12s;
text-transform:uppercase}
.rsw-tab:last-child{border-right:none}
.rsw-tab:hover{color:#888;background:#141414}
.rsw-tab.act{color:#fff;background:#161616;border-bottom:2px solid #fff}
/* ── content ── */
#rsw-content{flex:1;overflow-y:auto;overflow-x:hidden;background:#0c0c0c}
#rsw-content::-webkit-scrollbar{width:3px}
#rsw-content::-webkit-scrollbar-thumb{background:#222}
/* ── panels ── */
.rsw-pnl{display:none;padding:18px;height:100%;box-sizing:border-box}
.rsw-pnl.act{display:block}
/* ── sections ── */
.rsw-sec{background:#111;border:1px solid #1e1e1e;border-radius:4px;
padding:14px 16px;margin-bottom:12px}
.rsw-sec-hdr{font-size:8px;font-weight:900;letter-spacing:3px;color:#444;
text-transform:uppercase;margin-bottom:12px;display:flex;
align-items:center;justify-content:space-between}
.rsw-sec-hdr span{font-size:8px;color:#2a2a2a;font-weight:normal;letter-spacing:1px}
/* ── grid ── */
.rsw-grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
/* ── rows ── */
.rsw-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.rsw-row:last-child{margin-bottom:0}
.rsw-lbl{font-size:10px;color:#666;width:130px;flex-shrink:0;letter-spacing:.5px}
.rsw-inp{flex:1;background:#161616;border:1px solid #2a2a2a;color:#fff;
padding:8px 10px;font-size:12px;font-family:'Courier New',monospace;
outline:none;border-radius:2px;min-width:0}
.rsw-inp:focus{border-color:#444}
.rsw-sel{flex:1;background:#161616;border:1px solid #2a2a2a;color:#fff;
padding:8px 10px;font-size:11px;font-family:'Courier New',monospace;
outline:none;cursor:pointer;border-radius:2px}
/* ── buttons ── */
.rsw-btn{background:#161616;border:1px solid #333;color:#aaa;
padding:8px 16px;font-family:'Courier New',monospace;font-size:10px;
letter-spacing:1px;cursor:pointer;border-radius:2px;
transition:all .12s;white-space:nowrap}
.rsw-btn:hover{border-color:#555;color:#fff;background:#1e1e1e}
.rsw-btn.pri{background:#1a1a1a;border-color:#888;color:#fff}
.rsw-btn.pri:hover{background:#fff;color:#000;border-color:#fff}
.rsw-btn.grn{border-color:#2a4a2a;color:#4f4}
.rsw-btn.grn:hover{background:#0a1a0a;border-color:#4f4}
.rsw-btn.red{border-color:#4a1a1a;color:#f44}
.rsw-btn.red:hover{background:#1a0a0a;border-color:#f44}
.rsw-btn.act{background:#0a200a;border-color:#0f0;color:#0f0}
.rsw-btn.sm{padding:5px 10px;font-size:9px}
/* ── big attack buttons ── */
#rsw-atk-btns{display:grid;grid-template-columns:1fr 1fr;gap:10px}
#rsw-atk-btns .rsw-btn{padding:16px;text-align:center;font-size:12px;
font-weight:900;letter-spacing:3px;font-family:'Arial Black',Arial,sans-serif}
/* ── toggle rows ── */
.rsw-tr{display:flex;align-items:center;justify-content:space-between;
padding:9px 0;border-bottom:1px solid #161616}
.rsw-tr:last-child{border-bottom:none}
.rsw-tr-lbl{font-size:10px;color:#aaa;letter-spacing:.5px}
.rsw-tr-sub{font-size:8px;color:#333;display:block;margin-top:2px}
.rsw-tog{width:38px;height:18px;background:#1a1a1a;border:1px solid #2a2a2a;
border-radius:9px;position:relative;cursor:pointer;flex-shrink:0;
transition:all .15s}
.rsw-tog.on{background:#0d200d;border-color:#3a5a3a}
.rsw-tog::after{content:'';position:absolute;top:3px;left:3px;width:10px;height:10px;
background:#3a3a3a;border-radius:50%;transition:all .15s}
.rsw-tog.on::after{left:23px;background:#0f0}
/* ── camera preview ── */
#rsw-cam-wrap{border:1px solid #1e1e1e;background:#000;margin-bottom:10px;
display:flex;align-items:center;justify-content:center;height:240px}
#rsw-cam-prev{width:320px;height:240px;display:block}
/* ── log ── */
#rsw-log{background:#060606;border:1px solid #161616;border-radius:2px;
padding:12px;font-size:10px;color:#0f0;height:750px;
overflow-y:auto;white-space:pre;line-height:1.7;
font-family:'Courier New',monospace}
#rsw-log::-webkit-scrollbar{width:3px}
#rsw-log::-webkit-scrollbar-thumb{background:#1a1a1a}
/* ── show button ── */
#rsw-show{position:fixed;top:30px;left:30px;z-index:2147483646;
background:#111;border:1px solid #333;color:#fff;padding:7px 18px;
font-family:'Arial Black',Arial,sans-serif;font-size:12px;
font-weight:900;letter-spacing:4px;cursor:pointer;display:none;
border-radius:3px;box-shadow:0 4px 16px rgba(0,0,0,.8)}
/* ── dyn row ── */
#rsw-dyn-row{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}
.dnum{width:58px;background:#161616;border:1px solid #2a2a2a;color:#fff;
padding:7px 8px;font-size:11px;font-family:'Courier New',monospace;
outline:none;border-radius:2px}
#rsw-dyn-live-row{margin-top:8px;font-size:10px;color:#555;letter-spacing:.5px}
#rsw-dyn-live{color:#fff;font-weight:900}
/* score display */
#rsw-cur-score{font-size:28px;font-weight:900;color:#fff;text-align:center;
letter-spacing:2px;padding:8px 0;font-family:'Arial Black',Arial,sans-serif}
#rsw-cur-score span{font-size:12px;color:#444;font-weight:normal;display:block;
letter-spacing:3px;margin-top:2px}
/* country flag pill */
.rsw-flag{font-size:11px;margin-right:5px}
`;
function buildUI() {
if (document.getElementById('rsw')) return;
const st = document.createElement('style'); st.textContent = CSS;
document.head.appendChild(st);
const showBtn = document.createElement('button');
showBtn.id = 'rsw-show'; showBtn.textContent = 'RSWARE';
document.body.appendChild(showBtn);
/* ── build panel ── */
const root = document.createElement('div'); root.id = 'rsw';
/* title bar */
root.innerHTML = `
<div id="rsw-bar">
<div id="rsw-logo">RSWARE</div>
<div id="rsw-ver">v9.3</div>
<div id="rsw-spacer"></div>
<div class="rsw-pill" id="rsw-atk-pill">OFFLINE</div>
<div id="rsw-conf-status" style="font-size:8px;color:#333;letter-spacing:1px;margin-left:8px">AUTO</div>
<button id="rsw-close" style="margin-left:10px">✕</button>
</div>
<div id="rsw-tabs">
<div class="rsw-tab act" data-tab="score">SCORE</div>
<div class="rsw-tab" data-tab="camera">CAMERA</div>
<div class="rsw-tab" data-tab="audio">AUDIO</div>
<div class="rsw-tab" data-tab="spoof">SPOOF</div>
<div class="rsw-tab" data-tab="report">REPORT</div>
<div class="rsw-tab" data-tab="config">CONFIG</div>
<div class="rsw-tab" data-tab="log">LOG</div>
</div>
<div id="rsw-content">
<!-- ═══ SCORE TAB ═══ -->
<div class="rsw-pnl act" id="pnl-score">
<div class="rsw-grid2">
<!-- LEFT COLUMN -->
<div>
<!-- mode switch -->
<div class="rsw-sec">
<div class="rsw-sec-hdr">SCORE MODE</div>
<div style="display:flex;gap:8px;margin-bottom:14px">
<button id="btn-static" class="rsw-btn pri" style="flex:1;padding:12px;font-size:11px;letter-spacing:2px;font-weight:900">STATIC</button>
<button id="btn-dynamic" class="rsw-btn" style="flex:1;padding:12px;font-size:11px;letter-spacing:2px;font-weight:900">DYNAMIC</button>
</div>
<!-- STATIC view -->
<div id="rsw-static-view">
<div class="rsw-row">
<span class="rsw-lbl">FIXED SCORE</span>
<input id="rsw-val" class="rsw-inp" type="number" value="10" min="0" step="0.1"
style="font-size:22px;font-weight:900;color:#fff;text-align:center"/>
</div>
<div id="rsw-val-hint" style="font-size:9px;color:#444;text-align:right;margin-top:4px;letter-spacing:.5px">
server caps at 10 from frame scoring — WS layers send whatever you set
</div>
</div>
<!-- DYNAMIC view (hidden by default) -->
<div id="rsw-dyn-view" style="display:none">
<div style="display:flex;gap:8px;margin-bottom:10px">
<div style="flex:1">
<div style="font-size:8px;color:#444;letter-spacing:2px;margin-bottom:5px">MIN</div>
<input class="rsw-inp" id="dyn-min" type="number" value="7.0" min="0" step="0.1"
style="text-align:center;font-size:16px;font-weight:900;color:#fff;width:100%;box-sizing:border-box"/>
</div>
<div style="flex:1">
<div style="font-size:8px;color:#444;letter-spacing:2px;margin-bottom:5px">MAX</div>
<input class="rsw-inp" id="dyn-max" type="number" value="10.0" min="0" step="0.1"
style="text-align:center;font-size:16px;font-weight:900;color:#fff;width:100%;box-sizing:border-box"/>
</div>
<div style="width:80px">
<div style="font-size:8px;color:#444;letter-spacing:2px;margin-bottom:5px">EVERY (SEC)</div>
<input class="rsw-inp" id="dyn-int" type="number" value="3" min="1" step="1"
style="text-align:center;font-size:16px;font-weight:900;color:#fff;width:100%;box-sizing:border-box"/>
</div>
</div>
<div style="background:#0d0d0d;border:1px solid #1a1a1a;padding:10px 14px;display:flex;align-items:center;justify-content:space-between">
<span style="font-size:9px;color:#444;letter-spacing:1px">CURRENT VALUE</span>
<span id="rsw-dyn-live" style="font-size:24px;font-weight:900;color:#fff;font-family:'Arial Black',sans-serif">--</span>
</div>
</div>
</div>
<!-- big send value display -->
<div class="rsw-sec" style="background:#0d0d0d;text-align:center;padding:18px">
<div style="font-size:9px;color:#333;letter-spacing:3px;margin-bottom:6px">SENDING</div>
<div id="rsw-cur-score" style="font-size:42px;font-weight:900;color:#fff;font-family:'Arial Black',sans-serif;line-height:1">10.0</div>
<div id="rsw-cur-mode" style="font-size:8px;color:#333;letter-spacing:2px;margin-top:6px">STATIC</div>
</div>
<div id="rsw-atk-btns">
<button id="rsw-send" class="rsw-btn pri">SEND ATTACK</button>
<button id="rsw-stop" class="rsw-btn red">STOP ATTACK</button>
</div>
</div>
<!-- RIGHT COLUMN -->
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">METHOD</div>
<select id="rsw-method" class="rsw-sel" style="width:100%;margin-bottom:0">
<option value="all">All Layers (recommended)</option>
<option value="spoof">Frame Inject only</option>
<option value="display">Display Win only</option>
</select>
</div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">ENEMY SCORE</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">OVERRIDE ENEMY<span class="rsw-tr-sub">Set opponent score in live/result packets</span></span>
<div class="rsw-tog" id="t-enemy"></div>
</div>
<div class="rsw-row" style="margin-top:10px">
<span class="rsw-lbl">ENEMY VALUE</span>
<input id="rsw-enemy-val" class="rsw-inp" type="number" value="1.0" min="0" step="0.1"/>
</div>
</div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">DISPLAY PATCHES</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">DISPLAY WIN<span class="rsw-tr-sub">Patches match_result to show win</span></span>
<div class="rsw-tog" id="t-win"></div>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">LIVE SCORE BAR<span class="rsw-tr-sub">Patches incoming live_score bar</span></span>
<div class="rsw-tog" id="t-live"></div>
</div>
</div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">STATS</div>
<div class="rsw-row">
<span class="rsw-lbl">FRAMES PATCHED</span>
<span id="rsw-cnt" style="color:#fff;font-size:18px;font-weight:900">0</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">WS STATUS</span>
<span id="rsw-ws-status" style="color:#444;font-size:10px">not connected</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">ATTACK</span>
<div class="rsw-pill" id="rsw-atk-pill">OFFLINE</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ CAMERA TAB ═══ -->
<div class="rsw-pnl" id="pnl-camera">
<div class="rsw-grid2">
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">CAMERA PREVIEW</div>
<div id="rsw-cam-wrap">
<canvas id="rsw-cam-prev" width="320" height="240"></canvas>
</div>
<div style="font-size:9px;color:#333;text-align:center;letter-spacing:1px">
what opponent sees
</div>
</div>
</div>
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">CAMERA SPOOF</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENABLE<span class="rsw-tr-sub">Replace webcam with spoofed feed</span></span>
<div class="rsw-tog" id="t-cam"></div>
</div>
<div class="rsw-row" style="margin-top:12px">
<span class="rsw-lbl">MODE</span>
<select id="rsw-cam-mode" class="rsw-sel">
<option value="black">Solid Black</option>
<option value="color">Custom Color</option>
<option value="image">Upload Image</option>
</select>
</div>
<div class="rsw-row" id="rsw-cam-color-row" style="display:none">
<span class="rsw-lbl">COLOR</span>
<input id="rsw-cam-color" class="rsw-inp" type="color" value="#111111" style="height:36px;padding:2px 4px;cursor:pointer"/>
</div>
<div class="rsw-row" id="rsw-cam-img-row" style="display:none">
<span class="rsw-lbl">IMAGE</span>
<input id="rsw-cam-file" type="file" accept="image/*" style="display:none"/>
<button class="rsw-btn" id="rsw-cam-upload">UPLOAD IMAGE</button>
<span id="rsw-cam-fname" style="font-size:9px;color:#444;margin-left:6px"></span>
</div>
</div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">NOTE</div>
<div style="font-size:9px;color:#444;line-height:1.8;letter-spacing:.3px">
Camera spoof replaces the video stream opponents see.<br>
For best score results upload a face image so MediaPipe<br>
can detect landmarks — our hook still injects IDEAL_FACE<br>
before signing regardless of actual camera content.
</div>
</div>
</div>
</div>
</div>
<!-- ═══ AUDIO TAB ═══ -->
<div class="rsw-pnl" id="pnl-audio">
<div class="rsw-grid2">
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">MIC SPOOF</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENABLE<span class="rsw-tr-sub">Replace mic with silent/custom audio</span></span>
<div class="rsw-tog" id="t-mic"></div>
</div>
<div class="rsw-row" style="margin-top:12px">
<span class="rsw-lbl">MODE</span>
<select id="rsw-mic-mode" class="rsw-sel">
<option value="silent">Silent (mute yourself)</option>
<option value="tone">Sine Tone</option>
<option value="noise">White Noise</option>
</select>
</div>
<div class="rsw-row" id="rsw-mic-freq-row">
<span class="rsw-lbl">FREQUENCY</span>
<input id="rsw-mic-freq" class="rsw-inp" type="number" value="440" min="20" max="20000" step="10"/>
<span style="font-size:10px;color:#444">Hz</span>
</div>
</div>
</div>
<div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">INFO</div>
<div style="font-size:9px;color:#444;line-height:2;letter-spacing:.3px">
SILENT — opponent hears nothing from you<br>
TONE — sends a pure sine wave at chosen Hz<br>
NOISE — sends white noise<br><br>
Takes effect on the NEXT getUserMedia call.<br>
Refresh or reconnect to apply immediately.
</div>
</div>
</div>
</div>
</div>
<!-- ═══ SPOOF TAB ═══ -->
<div class="rsw-pnl" id="pnl-spoof">
<div class="rsw-grid2">
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">COUNTRY SPOOF</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENABLE<span class="rsw-tr-sub">Spoof navigator.language + geolocation</span></span>
<div class="rsw-tog" id="t-country"></div>
</div>
<div class="rsw-row" style="margin-top:12px">
<span class="rsw-lbl">COUNTRY</span>
<select id="rsw-country" class="rsw-sel"></select>
</div>
<div class="rsw-row">
<span class="rsw-lbl">LANGUAGE TAG</span>
<span id="rsw-country-lang" style="font-size:11px;color:#fff;letter-spacing:1px">en-US</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">TIMEZONE</span>
<span id="rsw-country-tz" style="font-size:10px;color:#888">America/New_York</span>
</div>
<button class="rsw-btn grn" id="rsw-apply-country" style="margin-top:6px">APPLY NOW</button>
</div>
</div>
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">PRO SPOOF</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENABLE<span class="rsw-tr-sub">4-layer PRO injection (fetch + XHR + localStorage + CSS)</span></span>
<div class="rsw-tog" id="t-pro"></div>
</div>
<div style="margin-top:12px;background:#0a0a0a;border:1px solid #1a1a1a;padding:10px 12px">
<div style="font-size:8px;color:#444;letter-spacing:2px;margin-bottom:8px">HOW IT WORKS</div>
<div style="font-size:9px;line-height:2;color:#333">
<span style="color:#555">LAYER 1</span> — fetch() hook patches all JSON from Supabase<br>
<span style="color:#555">LAYER 2</span> — XHR hook patches XMLHttpRequest responses<br>
<span style="color:#555">LAYER 3</span> — localStorage hook injects PRO into cached session<br>
<span style="color:#555">LAYER 4</span> — CSS removes paywall overlays / unlock blurred UI
</div>
</div>
<div style="margin-top:10px;background:#0a0a0a;border:1px solid #1a1a1a;padding:8px 12px;font-size:9px;color:#444;line-height:1.8">
Fields injected: <span style="color:#888">is_pro=true plan="pro" tier="pro"<br>
isPro=true hasPro=true subscription.active=true</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px">
<button class="rsw-btn grn" id="rsw-pro-reload" style="flex:1;font-weight:900;letter-spacing:1px">ENABLE + RELOAD PAGE</button>
</div>
<div style="font-size:8px;color:#333;margin-top:6px;text-align:center;letter-spacing:.5px">
reload applies the session patch before site JS reads it
</div>
</div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">BROWSER FINGERPRINT</div>
<div class="rsw-row">
<span class="rsw-lbl">LANGUAGE</span>
<span id="fp-lang" style="font-size:10px;color:#888">--</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">TIMEZONE</span>
<span id="fp-tz" style="font-size:10px;color:#888">--</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">PLATFORM</span>
<span id="fp-plat" style="font-size:10px;color:#888">--</span>
</div>
</div>
</div>
<!-- BAN BYPASS section — full-width below the 2-col grid -->
<div style="grid-column:1/-1">
<div class="rsw-sec" style="border-color:#2a1a1a">
<div class="rsw-sec-hdr" style="color:#f44">BAN BYPASS
<span style="color:#2a1a1a;font-size:8px;letter-spacing:1px">INTERCEPT + DROP ALL BAN SIGNALS</span>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENABLE<span class="rsw-tr-sub">Strip ban flags from API + WS + localStorage; spoof 403s; auto-reconnect</span></span>
<div class="rsw-tog" id="t-ban"></div>
</div>
<div style="margin-top:12px;background:#0a0a0a;border:1px solid #1e1010;padding:10px 12px">
<div style="font-size:8px;color:#444;letter-spacing:2px;margin-bottom:8px">LAYERS</div>
<div style="font-size:9px;line-height:2;color:#333">
<span style="color:#555">LAYER 1</span> — fetch() 403 intercept: if ban msg in body → fake 200 OK<br>
<span style="color:#555">LAYER 2</span> — fetch + XHR JSON: strips banned/is_banned/suspended/ban_expires…<br>
<span style="color:#555">LAYER 3</span> — localStorage Supabase session: wipes all ban fields on every read<br>
<span style="color:#555">LAYER 4</span> — WebSocket: drops ban/kick/suspend messages before JS sees them<br>
<span style="color:#555">LAYER 5</span> — WebSocket auto-reconnect if server closes with ban reason/code
</div>
</div>
<div style="display:flex;gap:8px;margin-top:10px">
<button class="rsw-btn red" id="rsw-ban-reconnect" style="flex:1;font-size:9px;letter-spacing:1px">FORCE RECONNECT NOW</button>
<button class="rsw-btn grn" id="rsw-ban-reload" style="flex:1;font-size:9px;letter-spacing:1px">ENABLE + RELOAD PAGE</button>
</div>
<div style="font-size:8px;color:#333;margin-top:8px;line-height:1.8">
If you see a ban screen: enable above → click RELOAD. The ban flag is wiped from your cached<br>
Supabase session before the site reads it, so the ban wall never renders.
</div>
</div>
</div>
</div>
</div>
<!-- ═══ REPORT TAB ═══ -->
<div class="rsw-pnl" id="pnl-report">
<div class="rsw-grid2">
<div>
<!-- CURRENT OPPONENT -->
<div class="rsw-sec" style="border-color:#2a3a2a">
<div class="rsw-sec-hdr" style="color:#4f4">CURRENT OPPONENT
<span>auto-detected from match data</span>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">NAME<span class="rsw-tr-sub">Captured from WS / API match messages</span></span>
<span id="rsw-rpt-opp-val" style="font-size:12px;font-weight:900;color:#444;letter-spacing:1px">--</span>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">USER ID<span class="rsw-tr-sub">Raw opponent_id / player2_id field</span></span>
<span id="rsw-rpt-opp-id" style="font-size:9px;color:#555;letter-spacing:.5px">--</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px">
<button class="rsw-btn grn" id="rsw-rpt-use-opp" style="font-weight:900;letter-spacing:1px">USE AS TARGET ↓</button>
<button class="rsw-btn sm" id="rsw-rpt-scan-dom">SCAN PAGE DOM</button>
</div>
<div style="font-size:8px;color:#333;margin-top:8px;line-height:1.8">
Opponent is captured automatically from every WS/API message<br>
the moment you enter a match. Click USE AS TARGET to fill below.<br>
SCAN PAGE DOM tries to find the name in visible page elements.
</div>
</div>
<!-- ENDPOINT CAPTURE -->
<div class="rsw-sec">
<div class="rsw-sec-hdr">ENDPOINT CAPTURE
<span>click native report button once to grab URL</span>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">ENDPOINT<span class="rsw-tr-sub">Recorded from your last report click</span></span>
<span id="rsw-rpt-captured" style="font-size:9px;color:#444;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">waiting…</span>
</div>
<div class="rsw-tr">
<span class="rsw-tr-lbl">PAYLOAD<span class="rsw-tr-sub">Body JSON from last report</span></span>
<span id="rsw-rpt-body" style="font-size:9px;color:#333;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">--</span>
</div>
<button class="rsw-btn sm" id="rsw-rpt-clear-cap" style="margin-top:8px">CLEAR CAPTURE</button>
</div>
</div>
<div>
<!-- TARGET + CONFIG -->
<div class="rsw-sec">
<div class="rsw-sec-hdr">TARGET</div>
<div class="rsw-row">
<span class="rsw-lbl">USER ID / NAME</span>
<input id="rsw-rpt-target" class="rsw-inp" type="text" placeholder="auto-filled from opponent"/>
</div>
<div class="rsw-row">
<span class="rsw-lbl">REASON</span>
<select id="rsw-rpt-reason" class="rsw-sel">
<option value="spam">Spam</option>
<option value="harassment">Harassment</option>
<option value="inappropriate">Inappropriate Content</option>
<option value="cheating">Cheating / Hacking</option>
<option value="hate_speech">Hate Speech</option>
<option value="impersonation">Impersonation</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">FLOOD CONFIG</div>
<div class="rsw-row">
<span class="rsw-lbl">REPORT COUNT</span>
<input id="rsw-rpt-count" class="rsw-inp" type="number" value="50" min="1" max="9999" style="font-size:18px;font-weight:900;text-align:center"/>
</div>
<div class="rsw-row">
<span class="rsw-lbl">DELAY (MS)</span>
<input id="rsw-rpt-delay" class="rsw-inp" type="number" value="400" min="50" max="60000"/>
<span style="font-size:9px;color:#444">between each</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px">
<button class="rsw-btn pri" id="rsw-rpt-start" style="padding:14px;font-size:11px;letter-spacing:2px;font-weight:900">MASS REPORT</button>
<button class="rsw-btn red" id="rsw-rpt-stop" style="padding:14px;font-size:11px;letter-spacing:2px;font-weight:900">STOP</button>
</div>
</div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">STATUS</div>
<div class="rsw-row">
<span class="rsw-lbl">STATE</span>
<div class="rsw-pill" id="rsw-rpt-pill">IDLE</div>
</div>
<div class="rsw-row">
<span class="rsw-lbl">REPORTS SENT</span>
<span id="rsw-rpt-sent" style="color:#fff;font-size:22px;font-weight:900;font-family:'Arial Black',sans-serif">0</span>
<span id="rsw-rpt-total" style="color:#333;font-size:11px;margin-left:6px">/ 0</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">LAST HTTP</span>
<span id="rsw-rpt-resp" style="color:#888;font-size:11px">--</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">MODE</span>
<span id="rsw-rpt-mode" style="color:#555;font-size:9px;letter-spacing:1px">--</span>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ CONFIG TAB ═══ -->
<div class="rsw-pnl" id="pnl-config">
<div class="rsw-grid2">
<div>
<div class="rsw-sec">
<div class="rsw-sec-hdr">SAVED SETTINGS</div>
<div style="font-size:9px;color:#444;line-height:1.9;margin-bottom:12px">
All toggles and values auto-save 0.7s after any change.<br>
Settings survive page refreshes — restored before page JS runs.<br>
Storage key: <span style="color:#666;letter-spacing:0">rsware_omoggle_v93</span>
</div>
<div class="rsw-row">
<span class="rsw-lbl">STATUS</span>
<span id="rsw-conf-status2" style="font-size:10px;color:#444;letter-spacing:1px">--</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px">
<button class="rsw-btn grn" id="rsw-conf-save">SAVE NOW</button>
<button class="rsw-btn red" id="rsw-conf-clear">CLEAR SAVED</button>
</div>
</div>
<div class="rsw-sec" style="background:#0d0d0d">
<div class="rsw-sec-hdr">CURRENT CONFIG SNAPSHOT</div>
<pre id="rsw-conf-preview" style="font-size:9px;color:#555;line-height:1.7;margin:0;overflow:auto;max-height:340px;white-space:pre-wrap"></pre>
</div>
</div>
<div>
<div class="rsw-sec" style="background:#0a0a0a">
<div class="rsw-sec-hdr">WHAT IS SAVED</div>
<div style="font-size:9px;color:#444;line-height:2.1">
<span style="color:#666">SCORE MODE</span> static/dynamic, value, min/max<br>
<span style="color:#666">DISPLAY WIN</span> on/off<br>
<span style="color:#666">LIVE SCORE BAR</span> on/off<br>
<span style="color:#666">ENEMY SCORE</span> on/off + override value<br>
<span style="color:#666">CAMERA</span> enabled, mode, color<br>
<span style="color:#666">MIC</span> enabled, mode, frequency<br>
<span style="color:#666">COUNTRY SPOOF</span> on/off + country code<br>
<span style="color:#666">PRO SPOOF</span> on/off<br>
<span style="color:#666">BAN BYPASS</span> on/off<br><br>
Camera images cannot be saved (binary data).<br>
Re-upload after each page reload.
</div>
</div>
</div>
</div>
</div>
<!-- ═══ LOG TAB ═══ -->
<div class="rsw-pnl" id="pnl-log">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
<span style="font-size:9px;color:#444;letter-spacing:2px">ACTIVITY LOG — frames patched: <span id="rsw-cnt2" style="color:#fff">0</span></span>
<button class="rsw-btn sm" id="rsw-log-clear">CLEAR</button>
</div>
<div id="rsw-log">-- RSWARE v9.0 ready --</div>
</div>
</div>
`;
document.body.appendChild(root);
/* ── populate country select ── */
const csel = document.getElementById('rsw-country');
Object.entries(COUNTRIES).forEach(function([code, c]) {
const o = document.createElement('option');
o.value = code; o.textContent = c.name;
if (code === 'US') o.selected = true;
csel.appendChild(o);
});
/* update fingerprint display */
document.getElementById('fp-lang').textContent = navigator.language || '--';
document.getElementById('fp-tz').textContent = Intl.DateTimeFormat().resolvedOptions().timeZone || '--';
document.getElementById('fp-plat').textContent = navigator.platform || '--';
/* ════════ event wiring ════════ */
/* close / show */
document.getElementById('rsw-close').onclick = function() {
root.classList.add('h'); showBtn.style.display = 'block';
};
showBtn.onclick = function() { root.classList.remove('h'); showBtn.style.display = 'none'; };
/* tabs */
root.querySelectorAll('.rsw-tab').forEach(function(tab) {
tab.onclick = function() {
root.querySelectorAll('.rsw-tab').forEach(t => t.classList.remove('act'));
root.querySelectorAll('.rsw-pnl').forEach(p => p.classList.remove('act'));
tab.classList.add('act');
document.getElementById('pnl-' + tab.dataset.tab).classList.add('act');
};
});
/* ── STATIC / DYNAMIC mode switch ── */
function setScoreMode(mode) {
DYN.enabled = (mode === 'dynamic');
document.getElementById('btn-static').className = 'rsw-btn ' + (DYN.enabled ? '' : 'pri');
document.getElementById('btn-dynamic').className = 'rsw-btn ' + (DYN.enabled ? 'pri' : '');
document.getElementById('rsw-static-view').style.display = DYN.enabled ? 'none' : 'block';
document.getElementById('rsw-dyn-view').style.display = DYN.enabled ? 'block' : 'none';
document.getElementById('rsw-cur-mode').textContent = DYN.enabled ? 'DYNAMIC' : 'STATIC';
if (DYN.enabled) {
DYN.min = Math.max(0, parseFloat(document.getElementById('dyn-min').value) || 7);
DYN.max = Math.max(0, parseFloat(document.getElementById('dyn-max').value) || 10);
if (DYN.min > DYN.max) { var tmp = DYN.min; DYN.min = DYN.max; DYN.max = tmp; }
var secs = Math.max(0.5, parseFloat(document.getElementById('dyn-int').value) || 3);
dynStart(secs * 1000);
log('[DYN] ON ' + DYN.min.toFixed(1) + '-' + DYN.max.toFixed(1) + ' every ' + secs + 's');
} else {
dynStop();
document.getElementById('rsw-dyn-live').textContent = '--';
document.getElementById('rsw-cur-score').textContent = CFG.scoreVal.toFixed(1);
log('[DYN] OFF fixed=' + CFG.scoreVal);
}
}
document.getElementById('btn-static').onclick = function() { setScoreMode('static'); };
document.getElementById('btn-dynamic').onclick = function() { setScoreMode('dynamic'); };
/* update DYN inputs live while in dynamic mode */
['dyn-min','dyn-max','dyn-int'].forEach(function(id) {
document.getElementById(id).addEventListener('input', function() {
if (!DYN.enabled) return;
DYN.min = Math.max(0, parseFloat(document.getElementById('dyn-min').value) || 7);
DYN.max = Math.max(0, parseFloat(document.getElementById('dyn-max').value) || 10);
if (DYN.min > DYN.max) { var tmp = DYN.min; DYN.min = DYN.max; DYN.max = tmp; }
var secs = Math.max(0.5, parseFloat(document.getElementById('dyn-int').value) || 3);
dynStart(secs * 1000); // restart with new interval
});
});
/* static score input */
document.getElementById('rsw-val').addEventListener('input', function() {
const v = parseFloat(this.value);
if (!isNaN(v) && v >= 0) {
CFG.scoreVal = v;
document.getElementById('rsw-cur-score').textContent = v.toFixed(1);
if (v > 10) document.getElementById('rsw-cur-mode').textContent = 'STATIC — server caps at 10';
else document.getElementById('rsw-cur-mode').textContent = 'STATIC';
log('[UI] score → ' + v); autoSave();
}
});
/* enemy val input */
document.getElementById('rsw-enemy-val').addEventListener('input', function() {
const v = parseFloat(this.value);
if (!isNaN(v) && v >= 0) { CFG.enemyVal = v; autoSave(); }
});
/* attack buttons */
document.getElementById('rsw-send').onclick = function() {
const m = document.getElementById('rsw-method').value;
if (m === 'spoof' || m === 'all') { CFG.scoreSpoof = true; startFlood(); }
if (m === 'display' || m === 'all') {
CFG.displayWin = true; CFG.liveSpoof = true;
document.getElementById('t-win').classList.add('on');
document.getElementById('t-live').classList.add('on');
}
this.classList.add('act');
document.getElementById('rsw-stop').classList.remove('act');
document.getElementById('rsw-atk-pill').textContent = 'ATTACKING';
document.getElementById('rsw-atk-pill').classList.add('on');
log('[ATTACK] START method=' + m + ' score=' + dynGet());
};
document.getElementById('rsw-stop').onclick = function() {
CFG.scoreSpoof = false; CFG.displayWin = false; CFG.liveSpoof = false;
stopFlood();
document.getElementById('t-win').classList.remove('on');
document.getElementById('t-live').classList.remove('on');
document.getElementById('rsw-send').classList.remove('act');
document.getElementById('rsw-atk-pill').textContent = 'OFFLINE';
document.getElementById('rsw-atk-pill').classList.remove('on');
log('[ATTACK] STOP');
};
/* extra toggles */
function mkTog(id, onFn, offFn) {
document.getElementById(id).onclick = function() {
this.classList.toggle('on');
const on = this.classList.contains('on');
if (on && onFn) onFn(); if (!on && offFn) offFn();
};
}
mkTog('t-win', function(){ CFG.displayWin=true; log('[UI] DISPLAY_WIN ON'); autoSave(); },
function(){ CFG.displayWin=false; log('[UI] DISPLAY_WIN OFF'); autoSave(); });
mkTog('t-live', function(){ CFG.liveSpoof=true; log('[UI] LIVE_BAR ON'); autoSave(); },
function(){ CFG.liveSpoof=false; log('[UI] LIVE_BAR OFF'); autoSave(); });
mkTog('t-enemy',function(){ CFG.enemySpoof=true; log('[UI] ENEMY_SCORE ON val='+CFG.enemyVal); autoSave(); },
function(){ CFG.enemySpoof=false; log('[UI] ENEMY_SCORE OFF'); autoSave(); });
/* camera */
document.getElementById('rsw-cam-mode').onchange = function() {
CAM.mode = this.value;
document.getElementById('rsw-cam-color-row').style.display = this.value==='color' ? 'flex' : 'none';
document.getElementById('rsw-cam-img-row').style.display = this.value==='image' ? 'flex' : 'none';
};
document.getElementById('rsw-cam-color').oninput = function() { CAM.color = this.value; };
document.getElementById('rsw-cam-upload').onclick = function() {
document.getElementById('rsw-cam-file').click();
};
document.getElementById('rsw-cam-file').onchange = function() {
if (!this.files || !this.files[0]) return;
const file = this.files[0];
document.getElementById('rsw-cam-fname').textContent = file.name;
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() { CAM.img = img; CAM.mode = 'image'; log('[CAM] image loaded: ' + file.name); };
img.src = e.target.result;
};
reader.readAsDataURL(file);
};
mkTog('t-cam', function(){ CAM.enabled=true; log('[CAM] enabled mode='+CAM.mode); autoSave(); },
function(){ CAM.enabled=false; log('[CAM] disabled'); autoSave(); });
/* mic */
document.getElementById('rsw-mic-mode').onchange = function() {
MIC.mode = this.value;
const freqRow = document.getElementById('rsw-mic-freq-row');
if (freqRow) freqRow.style.display = this.value === 'tone' ? 'flex' : 'none';
log('[MIC] mode → ' + this.value);
};
document.getElementById('rsw-mic-freq').addEventListener('input', function() {
const v = parseFloat(this.value);
if (!isNaN(v) && v > 0) { MIC.freq = v; log('[MIC] freq → ' + v + 'Hz'); }
});
mkTog('t-mic',
function() {
MIC.mode = document.getElementById('rsw-mic-mode').value || 'silent';
MIC.freq = parseFloat(document.getElementById('rsw-mic-freq').value) || 440;
MIC.enabled = true;
log('[MIC] enabled mode=' + MIC.mode + (MIC.mode==='tone'?' freq='+MIC.freq+'Hz':''));
},
function() {
MIC.enabled = false;
// stop any running nodes
_micNodes.forEach(function(n) { try { if (n.stop) n.stop(); if (n.disconnect) n.disconnect(); } catch(_) {} });
_micNodes.length = 0;
log('[MIC] disabled');
});
/* country */
document.getElementById('rsw-country').onchange = function() {
SPF.code = this.value;
const c = COUNTRIES[SPF.code];
document.getElementById('rsw-country-lang').textContent = c.lang;
document.getElementById('rsw-country-tz').textContent = c.tz;
};
document.getElementById('rsw-apply-country').onclick = function() {
SPF.country = true;
document.getElementById('t-country').classList.add('on');
applyCountrySpoof();
};
mkTog('t-country', function(){ SPF.country=true; applyCountrySpoof(); autoSave(); },
function(){ SPF.country=false; log('[SPF] country OFF'); autoSave(); });
/* BAN bypass */
mkTog('t-ban',
function() {
SPF.ban = true; autoSave();
log('[BAN] ON — all 5 ban layers active');
},
function() {
SPF.ban = false; autoSave();
clearTimeout(BAN_STATE.reconnectTimer);
log('[BAN] OFF');
}
);
document.getElementById('rsw-ban-reconnect').onclick = function() {
if (!BAN_STATE.lastUrl) { log('[BAN] no WS URL stored yet — join a match first'); return; }
log('[BAN] forcing reconnect → ' + BAN_STATE.lastUrl.split('?')[0]);
window.WebSocket(BAN_STATE.lastUrl, BAN_STATE.lastProto);
};
document.getElementById('rsw-ban-reload').onclick = function() {
SPF.ban = true;
document.getElementById('t-ban').classList.add('on');
// Wipe ban fields from stored session immediately before reload
try {
const raw = localStorage.getItem(SB_LS_KEY);
if (raw) {
const obj = JSON.parse(raw);
if (obj) { deepPatchBan(obj, 0); if(obj.user) deepPatchBan(obj.user, 0); localStorage.setItem(SB_LS_KEY, JSON.stringify(obj)); }
}
// Also scan all other LS keys for ban data
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i); if(!k) continue;
if (k.includes('auth') || k.includes('session') || k.includes('supabase') || k.startsWith('sb-')) {
try {
const v = localStorage.getItem(k);
if (!v) continue;
const o = JSON.parse(v);
if (o && typeof o === 'object') { deepPatchBan(o, 0); localStorage.setItem(k, JSON.stringify(o)); }
} catch(_) {}
}
}
} catch(_) {}
log('[BAN] session ban fields wiped — reloading in 600ms...');
setTimeout(function() { location.reload(); }, 600);
};
/* PRO bypass */
mkTog('t-pro',
function() {
SPF.pro = true; autoSave();
injectProCSS();
// Also force-patch the stored Supabase session right now
try {
const raw = localStorage.getItem(SB_LS_KEY);
if (raw) {
const obj = JSON.parse(raw);
if (obj && obj.user) { deepPatchPro(obj.user, 0); localStorage.setItem(SB_LS_KEY, JSON.stringify(obj)); }
}
} catch(_) {}
log('[PRO] ON — fetch+XHR+localStorage+CSS all active');
log('[PRO] Reload the page for full effect (stored session patched)');
},
function() { SPF.pro = false; autoSave(); log('[PRO] OFF'); }
);
document.getElementById('rsw-pro-reload').onclick = function() {
SPF.pro = true;
document.getElementById('t-pro').classList.add('on');
injectProCSS();
// patch stored session then reload
try {
const raw = localStorage.getItem(SB_LS_KEY);
if (raw) {
const obj = JSON.parse(raw);
if (obj && obj.user) { deepPatchPro(obj.user, 0); localStorage.setItem(SB_LS_KEY, JSON.stringify(obj)); }
}
} catch(e) {}
log('[PRO] session patched — reloading in 800ms...');
setTimeout(function() { location.reload(); }, 800);
};
/* config tab */
function refreshConfPreview() {
const el = document.getElementById('rsw-conf-preview');
if (el) el.textContent = JSON.stringify({
score:{spoof:CFG.scoreSpoof,val:CFG.scoreVal,dyn:DYN.enabled,min:DYN.min,max:DYN.max,displayWin:CFG.displayWin,liveSpoof:CFG.liveSpoof},
enemy:{spoof:CFG.enemySpoof,val:CFG.enemyVal},
cam:{enabled:CAM.enabled,mode:CAM.mode,color:CAM.color},
mic:{enabled:MIC.enabled,mode:MIC.mode,freq:MIC.freq},
spf:{country:SPF.country,code:SPF.code,pro:SPF.pro,ban:SPF.ban},
}, null, 2);
const s2 = document.getElementById('rsw-conf-status2');
if (s2) s2.textContent = localStorage.getItem(CONF_KEY) ? 'SAVED ✓' : 'NOT SAVED';
}
document.getElementById('rsw-conf-save').onclick = function() { saveConf(); refreshConfPreview(); log('[CONF] saved manually'); };
document.getElementById('rsw-conf-clear').onclick = function() { localStorage.removeItem(CONF_KEY); refreshConfPreview(); log('[CONF] cleared'); };
setInterval(refreshConfPreview, 2000);
/* log tab */
document.getElementById('rsw-log-clear').onclick = function() {
logs.length = 0;
document.getElementById('rsw-log').textContent = '';
};
/* ══ SYNC SAVED STATE TO UI ══ */
function syncUI() {
// SCORE
if (CFG.scoreVal != null) { document.getElementById('rsw-val').value = CFG.scoreVal; document.getElementById('rsw-cur-score').textContent = CFG.scoreVal.toFixed(1); }
if (DYN.enabled) { setScoreMode('dynamic'); document.getElementById('dyn-min').value=DYN.min; document.getElementById('dyn-max').value=DYN.max; }
if (CFG.displayWin){ document.getElementById('t-win').classList.add('on'); }
if (CFG.liveSpoof) { document.getElementById('t-live').classList.add('on'); }
if (CFG.enemySpoof){ document.getElementById('t-enemy').classList.add('on'); }
if (CFG.enemyVal != null) document.getElementById('rsw-enemy-val').value = CFG.enemyVal;
// CAM
document.getElementById('rsw-cam-mode').value = CAM.mode;
document.getElementById('rsw-cam-color').value = CAM.color;
if (CAM.mode==='color') document.getElementById('rsw-cam-color-row').style.display='flex';
if (CAM.mode==='image') document.getElementById('rsw-cam-img-row').style.display='flex';
if (CAM.enabled) document.getElementById('t-cam').classList.add('on');
// MIC
document.getElementById('rsw-mic-mode').value = MIC.mode;
document.getElementById('rsw-mic-freq').value = MIC.freq;
if (MIC.mode!=='tone') document.getElementById('rsw-mic-freq-row').style.display='none';
if (MIC.enabled) document.getElementById('t-mic').classList.add('on');
// COUNTRY
document.getElementById('rsw-country').value = SPF.code;
const c = COUNTRIES[SPF.code]||COUNTRIES.US;
document.getElementById('rsw-country-lang').textContent = c.lang;
document.getElementById('rsw-country-tz').textContent = c.tz;
if (SPF.country){ document.getElementById('t-country').classList.add('on'); applyCountrySpoof(); }
// PRO / BAN
if (SPF.pro){ document.getElementById('t-pro').classList.add('on'); injectProCSS(); }
if (SPF.ban){ document.getElementById('t-ban').classList.add('on'); }
// conf indicator
const cs = document.getElementById('rsw-conf-status');
if (cs) cs.textContent = localStorage.getItem(CONF_KEY) ? 'LOADED' : 'AUTO';
}
syncUI();
/* live status ticker — 250ms */
setInterval(function() {
/* log tab counter */
const c2 = document.getElementById('rsw-cnt2');
if (c2) c2.textContent = pktCount;
/* WS status */
const ws = document.getElementById('rsw-ws-status');
if (ws) ws.textContent = hookedWsRef
? (hookedWsRef.readyState === 1 ? 'connected ✓' : 'disconnected')
: 'not connected';
/* big score display */
const sc = document.getElementById('rsw-cur-score');
if (sc) sc.textContent = dynGet().toFixed(1);
/* dynamic live value */
if (DYN.enabled) {
const dl = document.getElementById('rsw-dyn-live');
if (dl) dl.textContent = DYN.cur.toFixed(1);
}
}, 250);
/* drag */
var drag=false, ox=0, oy=0;
document.getElementById('rsw-bar').addEventListener('mousedown', function(e){
drag=true; var r=root.getBoundingClientRect(); ox=e.clientX-r.left; oy=e.clientY-r.top; e.preventDefault();
});
document.addEventListener('mousemove', function(e){ if(!drag)return; root.style.left=(e.clientX-ox)+'px'; root.style.top=(e.clientY-oy)+'px'; });
document.addEventListener('mouseup', function(){ drag=false; });
/* INSERT */
document.addEventListener('keydown', function(e){
if (e.key==='Insert'){ var h=root.classList.toggle('h'); showBtn.style.display=h?'block':'none'; }
});
/* ════════ MASS REPORT ════════ */
function getAuthHeaders() {
const h = { 'Content-Type': 'application/json' };
try {
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i); if (!k) continue;
if (k.includes('auth-token') || k.includes('supabase') || k.startsWith('sb-')) {
try {
const obj = JSON.parse(localStorage.getItem(k) || '');
const tok = obj && (obj.access_token || (obj.session && obj.session.access_token));
if (tok) { h['Authorization'] = 'Bearer ' + tok; break; }
} catch(_) {}
}
}
} catch(_) {}
return h;
}
async function sendOneReport(target, reason) {
// ── Mode A: replay captured request ──
if (RPT.lastUrl && RPT.lastBody) {
try {
let body = RPT.lastBody;
if (target || reason) {
try {
const p = JSON.parse(body);
if (p && typeof p === 'object') {
if (target) ['userId','reportedId','reported_user_id','targetId','user_id','reported_id','reportedUserId'].forEach(function(fk){ if (fk in p) p[fk] = target; });
if (reason) ['reason','reportReason','report_reason','type','category'].forEach(function(fk){ if (fk in p) p[fk] = reason; });
body = JSON.stringify(p);
}
} catch(_) {}
}
const hdrs = Object.assign(getAuthHeaders(), RPT.lastHeaders || {});
// Always ensure Content-Type is json
hdrs['Content-Type'] = 'application/json';
const resp = await _origFetch(RPT.lastUrl, { method: 'POST', headers: hdrs, body });
return resp.status;
} catch(_) {}
}
// ── Mode B: try common REST endpoints ──
if (!target) return null;
const authH = getAuthHeaders();
const body = JSON.stringify({ userId: target, reportedId: target, reported_user_id: target, reason: reason || 'spam', report_reason: reason || 'spam', category: reason || 'spam' });
const endpoints = [
'/api/report', '/api/reports', '/api/user/report', '/api/users/report',
'/api/v1/report', '/api/v1/reports', '/report', '/reports',
];
for (const ep of endpoints) {
try {
const r = await _origFetch(ep, { method:'POST', headers:authH, body });
if (r.status < 500) return r.status;
} catch(_) {}
}
return null;
}
async function runReportLoop() {
const target = document.getElementById('rsw-rpt-target').value.trim();
const reason = document.getElementById('rsw-rpt-reason').value;
const delay = Math.max(50, parseInt(document.getElementById('rsw-rpt-delay').value) || 400);
if (!target && !RPT.lastBody) {
log('[RPT] ERROR: no target and no captured request — enter a user ID or capture first');
return;
}
const modeEl = document.getElementById('rsw-rpt-mode');
if (modeEl) modeEl.textContent = RPT.lastUrl ? 'AUTO-CAPTURE' : 'MANUAL FALLBACK';
while (RPT.running && RPT.sent < RPT.total) {
try {
const status = await sendOneReport(target, reason);
RPT.sent++;
const se = document.getElementById('rsw-rpt-sent');
if (se) se.textContent = RPT.sent;
const re = document.getElementById('rsw-rpt-resp');
if (re) re.textContent = status !== null ? String(status) : 'err';
if (RPT.sent % 10 === 0 || RPT.sent === 1)
log('[RPT] sent ' + RPT.sent + '/' + RPT.total + ' status=' + (status !== null ? status : 'err'));
} catch(e) {
log('[RPT] send error: ' + e.message);
}
if (RPT.sent < RPT.total) await new Promise(function(r){ setTimeout(r, delay); });
}
stopMassReport(true);
}
function startMassReport() {
if (RPT.running) return;
const count = Math.max(1, parseInt(document.getElementById('rsw-rpt-count').value) || 50);
RPT.running = true; RPT.sent = 0; RPT.total = count;
const pill = document.getElementById('rsw-rpt-pill');
if (pill) { pill.textContent = 'RUNNING'; pill.classList.add('on'); }
const tot = document.getElementById('rsw-rpt-total');
if (tot) tot.textContent = '/ ' + count;
const se = document.getElementById('rsw-rpt-sent');
if (se) se.textContent = '0';
const target = document.getElementById('rsw-rpt-target').value.trim();
const reason = document.getElementById('rsw-rpt-reason').value;
log('[RPT] START target=' + (target || '(auto)') + ' count=' + count
+ ' delay=' + document.getElementById('rsw-rpt-delay').value + 'ms'
+ ' mode=' + (RPT.lastUrl ? 'capture' : 'manual'));
runReportLoop();
}
function stopMassReport(auto) {
RPT.running = false;
const pill = document.getElementById('rsw-rpt-pill');
if (pill) { pill.textContent = 'IDLE'; pill.classList.remove('on'); }
if (!auto) log('[RPT] STOP total sent=' + RPT.sent);
else log('[RPT] DONE total sent=' + RPT.sent);
}
document.getElementById('rsw-rpt-use-opp').onclick = function() {
const val = MATCH.opponentId || MATCH.opponentUsername;
if (!val) { log('[RPT] no opponent detected yet — enter a match first'); return; }
document.getElementById('rsw-rpt-target').value = val;
log('[RPT] target set to opponent: ' + val);
};
document.getElementById('rsw-rpt-scan-dom').onclick = function() {
scanDOMForOpponent();
updateOpponentUI();
};
document.getElementById('rsw-rpt-start').onclick = startMassReport;
document.getElementById('rsw-rpt-stop').onclick = function(){ stopMassReport(false); };
document.getElementById('rsw-rpt-clear-cap').onclick = function() {
RPT.lastUrl = null; RPT.lastBody = null; RPT.lastHeaders = {};
const el = document.getElementById('rsw-rpt-captured'); if (el) { el.textContent = 'waiting…'; el.style.color='#444'; }
const be = document.getElementById('rsw-rpt-body'); if (be) { be.textContent = '--'; be.style.color='#333'; }
log('[RPT] capture cleared');
};
log('[RSWARE] v9.3 loaded — enable SCORE SPOOF before match');
log('[RSWARE] INSERT key = show/hide panel');
log('[RSWARE] REPORT tab: click native report once to capture, then MASS REPORT');
if (localStorage.getItem(CONF_KEY)) log('[CONF] settings restored from last session');
}
setupBanDOMScrubber();
if (document.readyState==='loading') document.addEventListener('DOMContentLoaded', buildUI);
else buildUI();
})();